如何同步等待取消异步任务

How to wait synchronously on cancellation of async task

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

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

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

该方法是同步调用的,DoTransaction改变对象,异步任务中也改变对象。例如,列表可以在迭代时更改。

以前,我在传递同步任务调度程序的任务上使用 ContinueWith 方法实现了此行为。总而言之,它很难理解,而且看起来有点脏。因此我想知道我是否可以使用新的异步等待功能实现相同的行为。这里的问题在于 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
        }
    }
}

是否有可能使用新的异步等待功能实现此行为而不会出现死锁?

如果每个 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<Task>,返回的 Task 将处于 Canceled 状态。

I have a generic class which runs certain work packages asynchronously.

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

The problem is, that the application is designed synchronously.

是的,但请注意,问题出在应用程序的设计上。等待任务完成本质上是一个异步操作,最佳解决方案绝对是.

Previously I achieved this behavior with the ContinueWith method on tasks where I passed a synchronous task scheduler.

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

I want to find a solution, where there is no refactoring necessary.

您真正要问的是 "how do I do sync-over-async"。 。但是在某些情况下,您可以使用一些 hack 来强制它工作。 没有始终有效的通用解决方案

这些 hack 是:只是阻塞,将操作推送到后台线程,以及 运行 嵌套的消息循环。 Stephen Toub's blog.

对它们进行了更详细的描述