gpt4 book ai didi

c# - Async/Await 相当于 .ContinueWith with CancellationToken 和 TaskScheduler.FromCurrentSynchronizationContext() 调度程序

转载 作者:太空宇宙 更新时间:2023-11-03 18:29:09 26 4
gpt4 key购买 nike

这是 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 工作可能仍在进行相当长一段时间)。

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

  3. updateUITask在某些情况下最终会被取消 task完成或出错(因为在执行 UpdateUI lambda 之前在 UI 线程上检查了 ct CancellationToken。

  4. CancellationToken 的检查之间没有流中断在 UI 线程和运行 UpdateUI lambda 。也就是说,如果 CancellationTokenSource 在 UI 线程上被取消,然后在检查 CancellationToken 之间没有竞争条件和 UpdateUI 的运行lambda——没有什么可以触发 CancellationToken在这两个事件之间,因为 UI 线程在这两个事件之间没有放弃。

讨论:

  • 我将其移动到 async/await 的主要目标之一是获取 UpdateUI lambda(为了便于阅读/调试)。

  • 上面#1 可以通过 Stephen Toub's WithCancellation task extension method 来解决. (您可以在答案中随意使用)。

  • 如果不通过UpdateUI,其他要求似乎很难封装到辅助方法中。作为 lambda,因为我在检查 await 之间不能有中断(即 CancellationToken )和执行 UpdateUI (因为我假设我不能依赖 await 使用 ExecuteSynchronously as mentioned here 的实现细节。这就是斯蒂芬谈论的神秘 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;
}
}

斯蒂芬·图布的 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;
}

相关链接:

最佳答案

写一个 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就像听起来一样简单;你替换了 ContinueWithawait .不过,大多数小碎片都可以清理很多。

m_cts.Cancel();
m_cts = new CancellationTokenSource();
var result = await Task.Run(() => LongRunningAndMightThrow())
.WithCancellation(m_cts.Token);
UpdateUI(result);

变化不大,但确实存在。您 [可能] 想在开始新操作时取消之前的操作。如果该要求不存在,请删除相应的行。取消逻辑都已由 WithCancellation 处理,如果请求取消,则无需显式抛出,因为这已经发生了。没有真正需要将任务或取消 token 存储为局部变量。 UpdateUI不应该接受 Task<bool> ,它应该只接受一个 bool 值。在调用 UpdateUI 之前,应该从任务中解包该值.

关于c# - Async/Await 相当于 .ContinueWith with CancellationToken 和 TaskScheduler.FromCurrentSynchronizationContext() 调度程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26305569/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com