本文介绍了异步/等待相当于.ContinueWith用的CancellationToken和TaskScheduler.FromCurrentSynchronizationContext()调度的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是一个后续this问题。

:这将是一个简洁的方式来恩preSS以下使用异步 / 的await ,而不是 .ContinueWith()

Question: What would be a succinct way to express the following using async/await instead of .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)

I'm mainly interested in the case of a UI SynchronizationContext (e.g. for Winforms)

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

Note with this that the behavior has all the following desired behaviors:


  1. 的CancellationToken 被取消, updateUITask 结束,尽快取消(即 LongRunningAndMightThrow 工作仍可能持续了相当长的一段时间)。

  1. When the CancellationToken is cancelled, the updateUITask ends up cancelled as soon as possible (i.e. the LongRunningAndMightThrow work may still be going on for quite some time).

克拉的CancellationToken被检查取消的在UI线程的前运行的updateUI拉姆达(见的)。

The ct CancellationToken gets checked for cancellation on the UI thread prior to running the UpdateUI lambda (see this answer).

updateUITask 最终会取消在某些情况下的任务完成或故障(因为克拉的CancellationToken在执行拉姆达的updateUI之前,UI线程检查。

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.

有中的的CancellationToken 在UI线程检查和运行之间的流动没有破的updateUI 拉姆达。也就是说,如果在 CancellationTokenSource 终止的只有的曾经取消了UI线程上,再有就是<$ C $的检查之间不存在竞争状态C>的CancellationToken 和的updateUI 拉姆达的运行 - 没有什么可以触发的CancellationToken 在这两个事件之间,因为这些两个事件之间的UI线程没有放弃在

There is no break in flow between the check of the CancellationToken on the UI thread and the running of the 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.

讨论:


  • 我的一个在移动这个主要目标,以异步/等待是让的updateUI 工作的的拉姆达的(对于方便阅读/调试性的)。

  • 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).

#以上1可以通过斯蒂芬Toub的 WithCancellation 任务扩展方法。 (你可以随意的答案使用)。

#1 above can be addressed by Stephen Toub's WithCancellation task extension method. (which you can feel free to use in the answers).

其他的要求似乎难以封装成一个辅助方法,无需通过的updateUI 作为一个lambda因为我不能有一个突破(即一个等待的CancellationToken 检查和的updateUI (间>),因为我假设我不能依赖于实现细节等待使用 ExecuteSynchronously 的提到的,这是它似乎有神秘的工作扩展方法 .ConfigureAwait(的CancellationToken)斯蒂芬谈会是非常有用的。

The other requirements seemed difficult to encapsulate into a helper method without passing UpdateUI as a lambda since I cannot have a break (i.e. 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.

我已经张贴了最佳答案我现在,但我希望有人会拿出更好的东西。

I've posted the best answer I have right now, but I'm hoping that someone will come up with something better.

样品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;
    }
}

斯蒂芬Toub的 WithCancellation 扩展方法:

Stephen Toub's WithCancellation extension method:

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; 
}

相关链接:






  • Equivalent of ContinueWith(delegate, CancellationToken) with await continuation
  • http://blogs.msdn.com/b/pfxteam/archive/2012/10/05/how-do-i-cancel-non-cancelable-async-operations.aspx
  • http://stackoverflow.com/a/15673072/495262
  • http://stackoverflow.com/a/17560746/495262

推荐答案

WithCancellation 方法可以做更简单,在短短一行code的:

Writing a WithCancellation method can be done much simpler, in just one line of code:

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);
}

至于你想做的事情,只是用操作等待而不是 ContinueWith 仅仅是因为它容易声音;你用的await 替换 ContinueWith 。大部分的小件虽然可以清理了很多。

As for the operation you want to do, just using await instead of ContinueWith is just as easy as it sounds; you replace the ContinueWith with an await. 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);

的变化并不大,但他们在那里。您[可能]要开始一个新的时取消了previous操作。如果需求不存在,删除相应的行。取消逻辑都已经通过 WithCancellation 处理,也没有必要,如果被请求消明确地抛出,因为这将已经发生。有没有真正的需要存储任务,或者取消标记,局部变量。 的updateUI 不应该接受任务&LT;布尔&GT; ,它应该只是接受一个布尔值。该值应该从任务的updateUI 被解开之前调用。

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. 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. The value should be unwrapped from the task before callingUpdateUI.

这篇关于异步/等待相当于.ContinueWith用的CancellationToken和TaskScheduler.FromCurrentSynchronizationContext()调度的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-22 14:53