如何同步等待取消异步任务
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)
假设 Keys
是 IEnumerable<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.
对它们进行了更详细的描述
我有一个通用的 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)
假设 Keys
是 IEnumerable<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 是:只是阻塞,将操作推送到后台线程,以及 运行 嵌套的消息循环。 Stephen Toub's blog.
对它们进行了更详细的描述