Mat*_*ith 1 c# task-parallel-library cancellation async-await
这是这个问题的后续行动.
问题:使用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);
Run Code Online (Sandbox Code Playgroud)
我主要对UI SynchronizationContext(例如Winforms)的情况感兴趣
请注意,该行为具有以下所有期望的行为:
当CancellationToken取消时,updateUITask最终会尽快取消(即LongRunningAndMightThrow工作可能仍会持续相当长的一段时间).
该ct的CancellationToken被检查取消在UI线程上运行UpdateUI拉姆达之前(见这个答案).
该updateUITask最终会取消在某些情况下task完成的或者是错误的(因为ct的CancellationToken在执行UpdateUI拉姆达之前在UI线程检查.
CancellationToken在UI线程的检查和UpdateUIlambda 的运行之间没有中断流.也就是说,如果CancellationTokenSource是唯一曾经在UI线程上取消了,再有就是的检查之间不存在竞争条件CancellationToken和的运行UpdateUI拉姆达-没有什么可以触发CancellationToken这两个事件之间,因为UI线程不放弃这两个事件之间.
讨论:
将其转换为异步/等待的主要目标之一是将UpdateUI工作从 lambda中取出(为了便于阅读/调试).
上面的#1可以通过Stephen Toub的WithCancellation任务扩展方法来解决.(你可以在答案中随意使用).
其他要求似乎很难封装成一个辅助方法没有通过UpdateUI的拉姆达因为我不能有一个突破(即await)的检查之间CancellationToken和执行UpdateUI(因为我以为我可以不依赖于实现细节await使用ExecuteSynchronously 如前所述这里似乎有斯蒂芬谈到的神话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;
}
}
Run Code Online (Sandbox Code Playgroud)
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;
}
Run Code Online (Sandbox Code Playgroud)
相关链接:
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);
}
Run Code Online (Sandbox Code Playgroud)
至于你想要做的操作,只需使用await而不是ContinueWith听起来那么简单; 你ContinueWith用一个替换await.尽管如此,大多数小件都可以清理干净.
m_cts.Cancel();
m_cts = new CancellationTokenSource();
var result = await Task.Run(() => LongRunningAndMightThrow())
.WithCancellation(m_cts.Token);
UpdateUI(result);
Run Code Online (Sandbox Code Playgroud)
变化并不大,但他们在那里.您[可能]想要在开始新操作时取消之前的操作.如果该要求不存在,请删除相应的行.取消逻辑全部已经处理WithCancellation,如果请求取消则不需要明确抛出,因为已经发生了.没有必要将任务或取消令牌存储为局部变量. UpdateUI不应该接受a Task<bool>,它应该只接受一个布尔值.在调用之前,应该从任务中解开该值UpdateUI.
| 归档时间: |
|
| 查看次数: |
1434 次 |
| 最近记录: |