简体   繁体   English

Async / Await等效于带有CancellationToken和TaskScheduler.FromCurrentSynchronizationContext()调度程序的.ContinueWith

[英]Async/Await equivalent to .ContinueWith with CancellationToken and TaskScheduler.FromCurrentSynchronizationContext() scheduler

This is a follow-up to this question . 这是该问题的后续措施。

Question : What would be a succinct way to express the following using async / await instead of .ContinueWith() ?: 问题 :使用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);

I'm mainly interested in the case of a UI SynchronizationContext (eg for Winforms) 我主要对UI SynchronizationContext的情况感兴趣(例如,对于Winforms)

Note with this that the behavior has all the following desired behaviors: 请注意,此行为具有以下所有所需行为:

  1. When the CancellationToken is cancelled, the updateUITask ends up cancelled as soon as possible (ie the LongRunningAndMightThrow work may still be going on for quite some time). CancellationTokenupdateUITask最终会尽快取消(即LongRunningAndMightThrow工作可能仍会持续一段时间)。

  2. The ct CancellationToken gets checked for cancellation on the UI thread prior to running the UpdateUI lambda (see this answer ). 在运行UpdateUI lambda之前,请在用户界面线程上检查ct CancellationToken是否取消(请参见此答案 )。

  3. The updateUITask will end up cancelled in some cases where the task completed or faulted (since the ct CancellationToken is checked on the UI thread before executing the UpdateUI lambda. 在某些情况下,如果task已完成或发生故障,则updateUITask最终将被取消(因为在执行UpdateUI lambda之前已在UI线程上检查了ct CancellationToken)。

  4. There is no break in flow between the check of the CancellationToken on the UI thread and the running of the UpdateUI lambda. 在UI线程上检查CancellationToken和运行UpdateUI lambda之间,流程没有中断。 That is, if the CancellationTokenSource is only ever cancelled on the UI thread, then there is no race condition between the checking of the CancellationToken and the running of the UpdateUI lambda--nothing could trigger the CancellationToken in between those two events because the UI thread is not relinquished in between those two events. 也就是说,如果CancellationTokenSource是永远只能在UI线程上取消了,再有就是的检查之间不存在竞争条件CancellationToken和的运行UpdateUI拉姆达-没有什么可以触发CancellationToken在这两个事件之间,因为UI线程在这两个事件之间不被放弃。

Discussion: 讨论:

  • One of my main goals in moving this to async/await is to get the UpdateUI work out of a lambda (for ease of readability/debuggability). 我将其移到异步/等待中的主要目的之一是使UpdateUI工作不再是lambda(为了易于阅读/调试)。

  • #1 above can be addressed by Stephen Toub's WithCancellation task extension method . 上面的#1可以通过Stephen Toub的WithCancellation任务扩展方法解决 (which you can feel free to use in the answers). (您可以在答案中随意使用)。

  • The other requirements seemed difficult to encapsulate into a helper method without passing UpdateUI as a lambda since I cannot have a break (ie an await ) between the checking of the CancellationToken and the executing of UpdateUI (because I assume I cannot rely on the implementation detail that await uses ExecuteSynchronously as mentioned here . This is where it seems that having the mythical Task extension method .ConfigureAwait(CancellationToken) that Stephen talks about would be very useful. 在不通过UpdateUI作为lambda的情况下,其他要求似乎很难封装到辅助方法中,因为我无法在CancellationToken的检查与UpdateUI的执行之间有一个中断(即await )(因为我假设我不能依赖实现细节) 就像这里提到的await使用ExecuteSynchronously ,这似乎对拥有Stephen讨论的神话般的Task扩展方法.ConfigureAwait(CancellationToken)很有用。

  • I've posted the best answer I have right now, but I'm hoping that someone will come up with something better. 我已经发布了目前最好的答案,但我希望有人会提出更好的建议。

Sample Winforms Application demonstrating the usage: 示例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's WithCancellation extension method: 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; 
}

Related Links: 相关链接:

Writing a WithCancellation method can be done much simpler, in just one line of code: 只需一行代码,编写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);
}

As for the operation you want to do, just using await instead of ContinueWith is just as easy as it sounds; 至于您要执行的操作,只需使用await而不是ContinueWith听起来就很容易。 you replace the ContinueWith with an await . 您将await替换为ContinueWith Most of the little pieces can be cleaned up a lot though. 大部分小东西虽然可以清理很多。

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

The changes are not huge, but they're there. 更改虽然不大,但是仍然存在。 You [probably] want to cancel the previous operation when starting a new one. 您[可能]要在开始新操作时取消上一个操作。 If that requirement doesn't exist, remove the corresponding line. 如果该要求不存在,请删除相应的行。 The cancellation logic is all already handled by WithCancellation , there is no need to throw explicitly if cancellation is requested, as that will already happen. 取消逻辑已由WithCancellation处理,如果请求取消,则无需显式抛出,因为这已经发生。 There's no real need to store the task, or the cancellation token, as local variables. 真正不需要将任务或取消令牌存储为局部变量。 UpdateUI shouldn't accept a Task<bool> , it should just accept a boolean. UpdateUI不应接受Task<bool> ,而应仅接受布尔值。 The value should be unwrapped from the task before calling UpdateUI . 在调用UpdateUI之前,应将该值从任务中解包。

The following should be equivalent: 以下内容应等效:

var task = Task.Run(() => LongRunningAndMightThrow());

m_cts = new CancellationTokenSource();
CancellationToken ct = m_cts.Token;

try
{
    await task.WithCancellation(ct);
}
finally
{
    ct.ThrowIfCancellationRequested();
    UpdateUI(task);
}

Notice that the try/finally is required for the case where the LongRunningAndMightThrow method faults, but by the time we return to the UI thread the CancellationToken has been triggered. 请注意,对于LongRunningAndMightThrow方法出错的情况,需要try/finally ,但是到我们返回UI线程时, CancellationToken已被触发。 Without it the returned outer Task would be faulted where in the original ContinueWith case, it would have been cancelled. 没有它,返回的外部Task将会出错,在原始ContinueWith情况下,它将被取消。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 .NET中的TaskScheduler.FromCurrentSynchronizationContext() - TaskScheduler.FromCurrentSynchronizationContext() in .NET TaskScheduler.FromCurrentSynchronizationContext块UI - TaskScheduler.FromCurrentSynchronizationContext block ui Task.ContinueWith(...,TaskScheduler.FromCurrentSynchronizationContext())在UI线程上运行的任何场景? - Any scenario where Task.ContinueWith(…, TaskScheduler.FromCurrentSynchronizationContext()) would *not* run on the UI thread? TaskScheduler.Current和TaskScheduler.FromCurrentSynchronizationContext()的区别? - TaskScheduler.Current and TaskScheduler.FromCurrentSynchronizationContext() difference? 使用TaskScheduler.FromCurrentSynchronizationContext更新Task中的UI - update UI in Task using TaskScheduler.FromCurrentSynchronizationContext TaskScheduler.FromCurrentSynchronizationContext()是否表示该类的上下文? - Does TaskScheduler.FromCurrentSynchronizationContext() mean the context of the class? 与等待继续的ContinueWith(delegate,CancellationToken)的等效 - Equivalent of ContinueWith(delegate, CancellationToken) with await continuation 为什么TaskScheduler.FromCurrentSynchronizationContext在Monotouch中不同步? - Why doesn't TaskScheduler.FromCurrentSynchronizationContext synchronize in Monotouch? 从工作线程调用TaskScheduler.FromCurrentSynchronizationContext异常 - TaskScheduler.FromCurrentSynchronizationContext exception when called from worker thread 关于TaskScheduler.FromCurrentSynchronizationContext和Task.Factory.StartNew()的使用 - Regarding usage of TaskScheduler.FromCurrentSynchronizationContext & Task.Factory.StartNew()
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM