Async / Await等同于.ContinueWith和CancellationToken以及TaskScheduler.FromCurrentSynchronizationContext()调度程序

时间:2014-10-10 18:12:33

标签: c# task-parallel-library async-await cancellation

这是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)

的情况感兴趣

请注意,该行为具有以下所有期望的行为:

  1. 取消CancellationToken后,updateUITask最终会被取消(即LongRunningAndMightThrow作品可能仍会持续一段时间。)< / p>

  2. 在运行UpdateUI lambda之前,会在UI线程上检查ct CancellationToken是否已取消(请参阅this answer)。

  3. updateUITask将在task完成或出现故障的某些情况下被取消(因为在执行UpdateUI lambda之前,在UI线程上检查了ct CancellationToken。

  4. UI线程上CancellationToken的检查与UpdateUI lambda的运行之间没有中断。也就是说,如果在{@ 1}}上<{1}} ,那么在检查CancellationTokenSource和{{1}的运行之间没有竞争条件lambda - 没有任何东西可以在这两个事件之间触发CancellationToken,因为在这两个事件之间不会放弃UI线程。

  5. 讨论:

    • 将此移至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

    相关链接:

2 个答案:

答案 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案例中,它会被取消。