【问题标题】:Use SynchronizationContext only on Task RanToCompletion仅在任务 RanToCompletion 上使用 SynchronizationContext
【发布时间】:2016-12-08 17:16:09
【问题描述】:

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

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

问题是“是否可以制作像 ContextIfSuccess() 这样的方法?”

【问题讨论】:

  • 为什么像SynchronizationContext 这样的实现细节对你很重要?
  • 因为我不想无缘无故地中断 UI 线程,尤其是在移动设备上
  • 然后使用异步缓冲区进行日志记录? log4j 提供了开箱即用的功能。另外,这真的是对性能的显着影响吗?
  • Log() 只是工作的一个例子,最好不要打扰 UI 线程
  • 请注意,当您将 try 更改为 button1.Text = await ... 时,您的方法会更加更简洁。那么result 变量或return 调用就没有理由了;它只是不必要地使代码复杂化。

标签: c# async-await task synchronizationcontext


【解决方案1】:

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

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

【讨论】:

  • 但是没有'await'的task.ContinueWith会在任何时候被GC处理掉吗?
  • @Miles ContinueWith 将在等待对象时调用。这是一个自定义的等待器,因此只要等待对象,编译器就会生成对OnCompleted 的调用。至于 GC 处理它,不,会有一个正在运行的任务对继续的引用,这将有来自调度程序的引用,它将被 root。您基本上永远不需要担心理论上可以在将来被收集的延续;任何可以触发其延续的任务都需要存在。
  • 感谢您的解释。我还有一个问题。那样做会更好吗? !Valid XHTML
【解决方案2】:

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

因此,您需要为此捕获 SynchronizationContext.CurrentPost

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

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

【讨论】:

  • 我明白你的意思,但我的想法是将其简单化为单一方法“ContextIfSuccess()”
  • 为什么不改用button1.Invoke
  • 我更喜欢 button1.Text = await GetDataAsync();而不是手动调用 UI 线程
  • @Miles 我错过了。添加。你为什么做这个?性能优化?
  • @usr 没错!对于移动系统
猜你喜欢
  • 2019-10-25
  • 1970-01-01
  • 2011-06-29
  • 2014-07-19
  • 1970-01-01
  • 1970-01-01
  • 2016-06-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多