【问题标题】:How to wait synchronously on cancellation of async task如何在取消异步任务时同步等待
【发布时间】:2015-02-18 09:31:45
【问题描述】:

我有一个异步运行某些工作包的通用类。它有可能取消所有任务的执行并同步等待取消的任务完成。在新事务(用户做某事)开始之前触发取消。这是必要的,因为异步任务和新事务会更改相同的对象,但异步任务会在假定事务之前的状态时执行此操作。

这是为什么这种行为如此重要的示例代码:

private void Transaction()
{
    asyncExecution.AbortAllAsyncWork();
    DoTransaction();
}

此方法被同步调用,DoTransaction 更改对象,这些对象在异步任务中也发生更改。例如列表可以在迭代时更改。

以前,我在传递同步任务调度程序的任务上使用 ContinueWith 方法实现了这种行为。总而言之,这很难理解,而且看起来有点脏。因此,我想知道是否可以使用新的 async-await 功能实现相同的行为。这里的问题在于here 描述的死锁。到目前为止出现死锁问题的代码:

public void RunAsync<TWork, TResult>(IIncrementalAsyncExecutable<TWork, TResult> executable, TWork initialWork) where TWork : class
{
    Task workingTask = RunAsyncInternal(executable, initialWork, CancellationTokenSource);
    if (IsRunning(workingTask))
    {
        workingTasks.Add(workingTask);
    }
}

private async Task RunAsyncInternal<TWork, TResult>(IIncrementalAsyncExecutable<TWork, TResult> executable, TWork initialWork, CancellationTokenSource tokenSource) where TWork : class
{
    while (!executable.WorkDone)
    {
        TResult result = await Task.Run(() => executable.CalculateNextStep(initialWork));
        executable.SyncResult(result);
        if (tokenSource.IsCancellationRequested)
        {
            return;
        }
    }
}

public void AbortAllAsyncWork()
{
    CancellationTokenSource.Cancel();
    foreach (Task workingTask in workingTasks)
    {
        if (IsRunning(workingTask))
        {
            workingTask.Wait(); // here is the deadlock problem
        }
    }
}

是否有可能通过新的 async-await 功能实现此行为而不会出现死锁?

【问题讨论】:

  • 在旁注中,为什么你需要RunAsyncInternal 中的while 循环?
  • 工作安排在药剂中,一旦完成,这些药剂就会同步回用户界面。

标签: c# .net multithreading asynchronous async-await


【解决方案1】:

我有一个异步运行某些工作包的通用类。

为此使用内置类型(例如 TPL 数据流)要容易得多。他们已经完成了所有艰苦的工作。

问题是,应用程序是同步设计的。

是的,但请注意问题出在应用程序的设计中。等待任务完成本身就是一个异步操作,最佳的解决方案肯定是Yuval's

之前我在传递同步任务调度程序的任务上使用 ContinueWith 方法实现了此行为。

我不明白这怎么可能避免你看到的僵局。

我想找到一个不需要重构的解决方案。

您真正要问的是“我如何进行异步同步”。 best answer is "don't"。但是您可以使用一些技巧来强制它在某些情况下工作。 没有始终有效的通用解决方案

这些技巧是:只需阻塞,将操作推送到后台线程,然后运行嵌套消息循环。在Stephen Toub's blog 上有更详细的描述。

【讨论】:

  • 好的,我认为在这种情况下,我的 hack 现在必须工作。还是谢谢。
【解决方案2】:

如果每个Task 都将被取消,您只需在它们上await 并抓住OperationCanceledException

public async Task AbortAllAsyncWork()
{
    CancellationTokenSource.Cancel();
    foreach (Task workingTask in workingTasks.Keys)
    {
        if (IsRunning(workingTask))
        {
            try
            {
                await workingTask;
            }
            catch (OperationCanceledException oce)
            {
                // Do something usefull
            }
        }
    }
}

或者,您可以简单地 await Task.WhenAll 处理所有任务:

await Task.WhenAll(workingTasks.Keys)

假设KeysIEnumerable&lt;Task&gt;,则返回的Task 将处于Canceled 状态。

【讨论】:

  • 这意味着当我调用 AbortAllAsyncWork 时,该方法在所有线程完成工作之前返回,不是吗?
  • @Yggdrasil 是的,它会在调用堆栈中产生更高的控制权。为什么这是个问题?它没有“完成”,它只是异步等待所有任务被取消。
  • @Yggdrasil 那是什么?构造函数?私有方法?
  • 请查看我对主要问题的编辑。无法在评论中显示。
  • @Yggdrasil 不要从private void 运行它,而是从private async Task 运行它,这样你就可以await
猜你喜欢
  • 1970-01-01
  • 2014-03-18
  • 2014-09-06
  • 1970-01-01
  • 1970-01-01
  • 2020-04-23
  • 1970-01-01
  • 1970-01-01
  • 2013-02-10
相关资源
最近更新 更多