仅在 Task RanToCompletion 上使用 SynchronizationContext

Use SynchronizationContext only on Task RanToCompletion

下面的代码解释了这个想法

    private async void button1_Click(object sender, EventArgs e)
    {
        string result;
        CancellationToken cancellationToken = new CancellationTokenSource(3000).Token;

        try
        {
            result = await GetDataAsync(cancellationToken)
                .ContextIfSuccess();    // Should use SynchronizationContext only if Task status is RanToCompletion
        }
        catch(OperationCanceledException)
        {
            /* Context is not required */
            return;
        }
        catch (Exception ex)
        {
            /* Context is not required otherwise it can slow down UI Thread a little bit */
            Log(ex.ToString());
            return;
        }

        /* UI Thread only */
        button1.Text = result;
    }

问题是"Is it possible to make the method like ContextIfSuccess() ?"

await 没有上下文 (ConfigureAwait(false))。然后,如果所需条件为真,则切换到上下文。

因此您需要捕获 SynchronizationContext.CurrentPost

这与 TaskAwaiter 所做的非常相似。它在没有上下文的情况下恢复,然后如果调用者需要则切换回上下文。

您应该可以将其变成 ContextIfSuccess 方法。基本上,克隆 TaskAwaiter 源代码并在完成通知中决定是否 Post 。我假设此功能已经存在。代码必须查看 ConfigureAwait(...) 值并有条件地应用上下文或不应用上下文。

为了获得您想要的方法,您需要创建自定义等待程序。它主要是样板文件,关键是当被要求添加一个延续时,你在成功完成时使用当前同步上下文添加一个到 运行,而在没有成功完成时使用默认调度程序添加一个 运行 完成.

public struct CaptureContextOnSuccessAwaiter : INotifyCompletion
{
    private Task task;

    public CaptureContextOnSuccessAwaiter(Task task)
    {
        this.task = task;
    }

    public CaptureContextOnSuccessAwaiter GetAwaiter() { return this; }

    public void OnCompleted(Action continuation)
    {
        if (SynchronizationContext.Current != null)
        {
            task.ContinueWith(t => continuation(),
                CancellationToken.None,
                TaskContinuationOptions.OnlyOnRanToCompletion,
                TaskScheduler.FromCurrentSynchronizationContext());
            task.ContinueWith(t => continuation(),
                CancellationToken.None,
                TaskContinuationOptions.NotOnRanToCompletion,
                TaskScheduler.Default);
        }
        else
        {
            task.ContinueWith(t => continuation(),
                CancellationToken.None,
                TaskContinuationOptions.None,
                TaskScheduler.Default);
        }
    }

    public void GetResult() { task.GetAwaiter().GetResult(); }
    public bool IsCompleted { get { return task.GetAwaiter().IsCompleted; } }
}

public struct CaptureContextOnSuccessAwaiter<T> : INotifyCompletion
{
    private Task<T> task;

    public CaptureContextOnSuccessAwaiter(Task<T> task)
    {
        this.task = task;
    }

    public CaptureContextOnSuccessAwaiter<T> GetAwaiter() { return this; }

    public void OnCompleted(Action continuation)
    {
        if (SynchronizationContext.Current != null)
        {
            task.ContinueWith(t => continuation(),
                CancellationToken.None,
                TaskContinuationOptions.OnlyOnRanToCompletion,
                TaskScheduler.FromCurrentSynchronizationContext());
            task.ContinueWith(t => continuation(),
                CancellationToken.None,
                TaskContinuationOptions.NotOnRanToCompletion,
                TaskScheduler.Default);
        }
        else
        {
            task.ContinueWith(t => continuation(),
                CancellationToken.None,
                TaskContinuationOptions.None,
                TaskScheduler.Default);
        }
    }

    public T GetResult() { return task.GetAwaiter().GetResult(); }
    public bool IsCompleted { get { return task.GetAwaiter().IsCompleted; } }
}
public static CaptureContextOnSuccessAwaiter ContextIfSuccess(this Task task)
{
    return new CaptureContextOnSuccessAwaiter(task);
}

public static CaptureContextOnSuccessAwaiter<T> ContextIfSuccess<T>(this Task<T> task)
{
    return new CaptureContextOnSuccessAwaiter<T>(task);
}