【发布时间】:2016-03-21 14:09:47
【问题描述】:
我正在使用 C# 中的一些线程构造,但遇到了一些不符合我对锁定工作原理的理解的问题。我有一个接受异步任务的辅助函数,并使用TaskCompletionSource 成员在多次调用时尝试同步访问。
public static void Main(string[] args)
{
var test = new TestClass();
var task1 = test.Execute("First Task", async () => await Task.Delay(1000));
var task2 = test.Execute("Second Task", async () => await Task.Delay(1000));
task1.Wait();
task2.Wait();
Console.ReadLine();
}
class TestClass : IDisposable
{
private readonly object _lockObject = new object();
private TaskCompletionSource<bool> _activeTaskCompletionSource;
public async Task Execute(string source, Func<Task> actionToExecute)
{
Task activeTask = null;
lock (_lockObject)
{
if (_activeTaskCompletionSource != null)
{
activeTask = _activeTaskCompletionSource.Task;
}
else
{
_activeTaskCompletionSource = new TaskCompletionSource<bool>();
}
}
while (activeTask != null)
{
await activeTask;
lock (_lockObject)
{
if (_activeTaskCompletionSource != null)
{
activeTask = _activeTaskCompletionSource.Task;
}
else
{
activeTask = null;
}
}
}
await actionToExecute();
lock (_lockObject)
{
_activeTaskCompletionSource.SetResult(true);
_activeTaskCompletionSource = null;
}
}
}
对于第二个任务,这总是会陷入无限循环。我放了一些代码来记录每个步骤,它总是产生这样的东西(我在#s之后手动插入了cmets):
[第一个任务]等待锁定(设置) [第一个任务]进入锁(设置) [第一个任务]抓取'_activeTaskCompletionSource'(设置) [第一个任务] 解锁(设置) [第一个任务]正在运行... [第二个任务] 等待锁定(设置) [第二个任务] 进入锁(设置) [第二个任务] 分配“activeTask”(设置) [第二个任务] 解锁(设置) [第二个任务] 等待任务完成... 【第一个任务】完成! 【第一个任务】等待锁(清理) 【第一个任务】进入锁(清理) [第一个任务]设置_activeTaskCompletionSource结果... # 永远不会到达'_activeTaskCompletionSource = null' # 第一个任务永远不会“释放锁(清理)” 【第二个任务】等待的任务完成! 【第二个任务】等待锁(循环) # 'await' 完成后立即进入锁 # 不等待“第一个任务”完成锁定! 【第二个任务】进入锁(循环) [第二个任务]分配'activeTask'(循环) [第二个任务] 锁释放(循环) [第二个任务] 等待任务完成... 【第二个任务】等待的任务完成!这最终将第二个任务送入无限循环,因为_activeTaskCompletionSource 永远不会设置回null。
我的印象是 没有其他线程 可以进入锁,直到所有先前的线程都退出它,但在这里,我的 First Task 线程永远不会完成并在 @ 之前释放它的清理锁987654326@线程抓住了它。
这和混合锁和异步/等待有什么关系吗?
【问题讨论】:
-
绝对不建议将 task.Wait() 或 task.Result 设置为异步任务,因为它很容易陷入死锁情况。
-
@OsmanEsen 使用此示例中的
task.Wait()调用只是因为它在控制台应用程序中运行,而在主函数中不可能使用async/await。我已经避免在所有异步函数本身以及整个TestClass实现中使用阻塞调用。
标签: c# multithreading locking task-parallel-library