线程安全任务队列正在等待



我想做一件简单的事情(假设ContinueWith是线程安全的(:

readonly Task _task = Task.CompletedTask;
// thread A: conditionally prolong the task
if(condition)
_task.ContinueWith(o => ...);
// thread B: await for everything
await _task;

问题:在上面的代码中,await _task立即返回,忽略是否有内部任务。

扩展_task可以扩展多个ContinueWith的要求,我该如何等待所有这些都完成?


当然我可以尝试用旧的线程安全方式:

Task _task = Task.CompletedTask;
readonly object _lock = new object();
// thread A
if(condition)
_lock(_lock)
_task = _task.ContinueWith(o => ...); // storing the latest inner task
// thread B
lock(_lock)
await _task;

或者将任务存储在线程安全集合中并使用Task.WaitAll

但我很好奇第一个片段是否有简单的修复方法?

您的旧版本还可以,除了保持锁定的await;您应该在持有锁的同时将_task复制到本地变量,释放锁,然后才复制await

但ContinueWith工作流并不是实现该逻辑的最佳方式。ContinueWith是在.NET 4.0中引入的,在异步等待成为该语言的一部分之前。

在现代C#中,我认为最好这样做:

static async Task runTasks()
{
if( condition1 )
await handle1();
if( condition2 )
await handle2();
}
readonly Task _task = runTasks();

或者如果你想让它们并行运行:

IEnumerable<Task> parallelTasks()
{
if( condition1 )
yield return handle1();
if( condition2 )
yield return handle2();
}
readonly Task _task = Task.WhenAll( parallelTasks() );

附言:如果条件是动态变化的,对于顺序版本,您应该在第一个等待之前将其值复制到局部变量。

更新:所以你是说你的线程A延长了任务以响应动态发生的一些事件?如果是的话,那么您的旧方法是可以的,它很简单而且有效,只需修复lock(){ await ... }并发错误即可。

从理论上讲,更清洁的方法可能有点像反应堆模式,但不幸的是,它要复杂得多。这是我的一些稍微相似的开源示例。您不需要并发限制信号量,也不需要第二个队列,但您需要公开Task类型的属性,用CompletedTask初始化它,在发布第一个任务时用TaskCompletionSource<bool>从post方法创建的新任务替换,并在没有更多发布任务时在runAsyncProcessor方法中完成该任务。

在原始代码中,await _task;会立即返回,因为task已完成。你需要做

_task = _task.ContinueWith(...);

你不必使用锁。你在同一条线上工作。在您的"锁定"版本的代码中,您确实使用了_task = _task.ContinueWith(...),这就是它工作的原因,而不是因为锁定。


EDIT:刚才看到您想在一个线程中执行_task = _task.ContinueWith(...);_task = _task.ContinueWith(...)

这和设计一样糟糕。永远不要将线程和任务结合在一起。您应该选择仅任务的解决方案,或者选择仅线程的解决方案。

如果您选择仅限任务的解决方案(推荐(,只需创建一个良好的工作任务序列,然后根据前一个任务的结果决定如何执行每个任务。

如果您选择仅限线程的解决方案,则可能需要使用ManualResetEvent句柄来同步您的任务。

最新更新