这是this question的后续行动。
问题:使用async
/ await
代替.ContinueWith()
表达以下内容的简洁方法是什么?:
var task = Task.Run(() => LongRunningAndMightThrow());
m_cts = new CancellationTokenSource();
CancellationToken ct = m_cts.Token;
var uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task updateUITask = task.ContinueWith(t => UpdateUI(t), ct, TaskContinuationOptions.None, uiTaskScheduler);
我主要对UI SynchronizationContext(例如Winforms)
的情况感兴趣请注意,该行为具有以下所有期望的行为:
取消CancellationToken
后,updateUITask
最终会被取消(即LongRunningAndMightThrow
作品可能仍会持续一段时间。)< / p>
在运行UpdateUI lambda之前,会在UI线程上检查ct
CancellationToken是否已取消(请参阅this answer)。
updateUITask
将在task
完成或出现故障的某些情况下被取消(因为在执行UpdateUI lambda之前,在UI线程上检查了ct
CancellationToken。
UI线程上CancellationToken
的检查与UpdateUI
lambda的运行之间没有中断。也就是说,如果在{@ 1}}上<{1}} 仅 ,那么在检查CancellationTokenSource
和{{1}的运行之间没有竞争条件lambda - 没有任何东西可以在这两个事件之间触发CancellationToken
,因为在这两个事件之间不会放弃UI线程。
讨论:
将此移至async / await的主要目标之一是获取lambda的UpdateUI
工作 (为了便于阅读/调试)。
#1可以通过Stephen Toub's WithCancellation
task extension method解决。 (您可以随意使用答案)。
其他要求似乎很难在不将CancellationToken
作为lambda传递的情况下封装到辅助方法中,因为在检查{{1}之间我不能有中断(即UpdateUI
)和UpdateUI
的执行(因为我假设我不能依赖await
使用CancellationToken
as mentioned here的实现细节。这就是看起来神秘的地方{ {1}}斯蒂芬谈到的扩展方法UpdateUI
非常有用。
我发布了我现在最好的答案,但我希望有人会提出更好的答案。
示例Winforms应用程序演示用法:
await
Stephen Toub的ExecuteSynchronously
扩展方法:
Task
相关链接:
答案 0 :(得分:5)
只需一行代码就可以更简单地编写WithCancellation
方法:
public static Task WithCancellation(this Task task,
CancellationToken token)
{
return task.ContinueWith(t => t.GetAwaiter().GetResult(), token);
}
public static Task<T> WithCancellation<T>(this Task<T> task,
CancellationToken token)
{
return task.ContinueWith(t => t.GetAwaiter().GetResult(), token);
}
至于您要执行的操作,只使用await
代替ContinueWith
就像听起来一样简单;您将ContinueWith
替换为await
。尽管如此,大多数小件都可以清理干净。
m_cts.Cancel();
m_cts = new CancellationTokenSource();
var result = await Task.Run(() => LongRunningAndMightThrow())
.WithCancellation(m_cts.Token);
UpdateUI(result);
变化并不大,但他们就在那里。您[可能]想要在开始新操作时取消之前的操作。如果该要求不存在,请删除相应的行。取消逻辑都已由WithCancellation
处理,如果请求取消,则无需显式抛出,因为这已经发生。没有必要将任务或取消令牌存储为局部变量。 UpdateUI
不应该接受Task<bool>
,它应该只接受布尔值。在调用UpdateUI
之前,应该从任务中解包该值。
答案 1 :(得分:2)
以下内容应相同:
var task = Task.Run(() => LongRunningAndMightThrow());
m_cts = new CancellationTokenSource();
CancellationToken ct = m_cts.Token;
try
{
await task.WithCancellation(ct);
}
finally
{
ct.ThrowIfCancellationRequested();
UpdateUI(task);
}
请注意,try/finally
方法出错的情况需要LongRunningAndMightThrow
,但是当我们返回到UI线程时,CancellationToken
已被触发。如果没有它,返回的外部Task
将会出现故障。在最初的ContinueWith
案例中,它会被取消。