这是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
工作可能仍会进行一段时间)。在运行UpdateUI lambda之前,请在UI线程上检查
ct
CancellationToken是否取消(请参见this answer)。在
updateUITask
完成或出现故障的某些情况下,task
将最终被取消(因为在执行UpdateUI lambda之前,已在UI线程上检查了ct
CancellationToken)。在UI线程上检查
CancellationToken
和运行UpdateUI
lambda之间,流程没有中断。也就是说,如果CancellationTokenSource
仅在UI线程上被取消,则在CancellationToken
的检查与UpdateUI
lambda的运行之间不存在竞争条件-任何事情都不会触发CancellationToken
在这两个事件之间切换,因为在这两个事件之间不放弃UI线程。讨论:
我将其移到异步/等待中的主要目的之一是使
UpdateUI
工作脱离lambda(为便于阅读/调试)。上面的#1可以用Stephen Toub's
WithCancellation
task extension method寻址。 (您可以在答案中随意使用)。在不通过
UpdateUI
作为lambda的情况下,其他要求似乎很难封装到辅助方法中,因为在await
的检查和CancellationToken
的执行之间我不能有一个中断(即UpdateUI
)(因为我假设我不能依赖await
使用ExecuteSynchronously
as mentioned here的实现细节,这似乎是Stephen讨论的神话般的Task
扩展方法.ConfigureAwait(CancellationToken)
很有用的地方。我已经发布了目前最好的答案,但我希望有人会提出更好的建议。
示例Winforms应用程序演示了用法:
public partial class Form1 : Form
{
CancellationTokenSource m_cts = new CancellationTokenSource();
private void Form1_Load(object sender, EventArgs e)
{
cancelBtn.Enabled = false;
}
private void cancelBtn_Click(object sender, EventArgs e)
{
m_cts.Cancel();
cancelBtn.Enabled = false;
doWorkBtn.Enabled = true;
}
private Task DoWorkAsync()
{
cancelBtn.Enabled = true;
doWorkBtn.Enabled = false;
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);
return updateUITask;
}
private async void doWorkBtn_Click(object sender, EventArgs e)
{
try
{
await DoWorkAsync();
MessageBox.Show("Completed");
}
catch (OperationCanceledException)
{
MessageBox.Show("Cancelled");
}
catch
{
MessageBox.Show("Faulted");
}
}
private void UpdateUI(Task<bool> t)
{
// We *only* get here when the cancel button was *not* clicked.
cancelBtn.Enabled = false;
doWorkBtn.Enabled = true;
// Update the UI based on the results of the task (completed/failed)
// ...
}
private bool LongRunningAndMightThrow()
{
// Might throw, might complete
// ...
return true;
}
}
Stephen Toub的
WithCancellation
扩展方法:public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<bool>();
using(cancellationToken.Register(s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs))
if (task != await Task.WhenAny(task, tcs.Task))
throw new OperationCanceledException(cancellationToken);
return await task;
}
相关链接:
Equivalent of ContinueWith(delegate, CancellationToken) with await continuation
http://blogs.msdn.com/b/pfxteam/archive/2012/10/05/how-do-i-cancel-non-cancelable-async-operations.aspx
https://stackoverflow.com/a/15673072/495262
https://stackoverflow.com/a/17560746/495262
最佳答案
只需一行代码,编写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
之前,应从任务中解开该值。关于c# - Async/Await等效于带有CancellationToken和TaskScheduler.FromCurrentSynchronizationContext()调度程序的.ContinueWith,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/26305569/