0

我想做一件简单的事情(假设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.

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

4

2 回答 2

2

你的旧版本没问题,除了await保持锁;您应该在持有锁的同时将其复制_task到局部变量,释放锁,然后await

但是,ContinueWith 工作流程不是 IMO 实现该逻辑的最佳方式。在 async-await 成为语言的一部分之前,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() );

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

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

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

于 2019-11-18T10:44:13.893 回答
-1

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

_task = _task.ContinueWith(...);

您不必使用锁。您正在同一个线程中工作。在代码的“锁”版本中,您确实使用_task = _task.ContinueWith(...)了它,这就是它起作用的原因,而不是因为锁。


编辑:刚刚看到你想_task = _task.ContinueWith(...);在一个线程中做和_task = _task.ContinueWith(...).

这作为设计是相当糟糕的。你永远不应该将线程和任务结合起来。您应该选择仅任务解决方案或仅线程解决方案。

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

如果您选择纯线程解决方案,您可能希望使用ManualResetEvent句柄来同步您的任务。

于 2019-11-18T10:22:27.807 回答