【发布时间】:2014-12-05 23:40:10
【问题描述】:
这是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 线程上检查
ctCancellationToken 是否取消(请参阅this answer)。-
在
李>task完成或出错的某些情况下,updateUITask最终会被取消(因为在执行 UpdateUI lambda 之前在 UI 线程上检查了ctCancellationToken。 在 UI 线程上检查
CancellationToken和运行UpdateUIlambda 之间没有中断。也就是说,如果CancellationTokenSource在UI 线程上仅 被取消,那么在CancellationToken的检查和UpdateUIlambda 的运行之间没有竞争条件--没有可能会在这两个事件之间触发CancellationToken,因为在这两个事件之间没有放弃 UI 线程。
讨论:
-
我将其移至 async/await 的主要目标之一是让
UpdateUI工作输出 lambda(为了便于阅读/调试)。 上面的#1 可以通过Stephen Toub's
WithCancellationtask extension method 解决。 (您可以随意在答案中使用)。如果不将
UpdateUI作为 lambda 传递,其他要求似乎很难封装到辅助方法中,因为在检查CancellationToken和执行UpdateUI(因为我假设我不能依赖await使用ExecuteSynchronouslyas mentioned here 的实现细节。这似乎是拥有神话般的Task扩展方法.ConfigureAwait(CancellationToken)Stephen 谈到的有用。我已经发布了目前最好的答案,但我希望有人能提出更好的答案。
演示用法的示例 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;
}
相关链接:
【问题讨论】:
标签: c# task-parallel-library async-await cancellation