为什么从Async CTP / Release中删除“SwitchTo”?

时间:2013-03-12 14:05:15

标签: c# asynchronous async-ctp async-await

我今天尝试使用SwitchTo方法切换到GUI线程,并发现我解除它的示例不起作用,只是因为该方法不存在。

然后我发现了这个模糊here

  

我们摆脱它的原因是因为它太危险了。另一种方法是将您的代码捆绑在TaskEx.Run ...

我的问题很简单:为什么是危险的?使用它会带来哪些特定的危险?

请注意,我阅读该帖子的其余部分,因此我了解此处存在技术限制。我的问题仍然是,如果我意识到这一点,为什么危险

我正在考虑重新实现帮助方法来为我提供指定的功能,但是如果有某些根本原因被破坏,除了有人认为这是危险的,我不会这样做。

具体来说,非常天真,我会考虑如何实施所需的方法:

public static class ContextSwitcher
{
    public static ThreadPoolContextSwitcher SwitchToThreadPool()
    {
        return new ThreadPoolContextSwitcher();
    }

    public static SynchronizationContextSwitcher SwitchTo(this SynchronizationContext synchronizationContext)
    {
        return new SynchronizationContextSwitcher(synchronizationContext);
    }
}

public class SynchronizationContextSwitcher : INotifyCompletion
{
    private readonly SynchronizationContext _SynchronizationContext;

    public SynchronizationContextSwitcher(SynchronizationContext synchronizationContext)
    {
        _SynchronizationContext = synchronizationContext;
    }

    public SynchronizationContextSwitcher GetAwaiter()
    {
        return this;
    }

    public bool IsCompleted
    {
        get
        {
            return false;
        }
    }

    public void OnCompleted(Action action)
    {
        _SynchronizationContext.Post(_ => action(), null);
    }

    public void GetResult()
    {
    }
}

public class ThreadPoolContextSwitcher : INotifyCompletion
{
    public ThreadPoolContextSwitcher GetAwaiter()
    {
        return this;
    }

    public bool IsCompleted
    {
        get
        {
            return false;
        }
    }

    public void OnCompleted(Action action)
    {
        ThreadPool.QueueUserWorkItem(_ => action(), null);
    }

    public void GetResult()
    {
    }
}

这将允许我编写这样的代码:

public async void Test()
{
    await ContextSwitcher.SwitchToThreadPool(); // ensure we're not bogging down the UI thread
    // do some heavy processing
    await _UIContext.SwitchTo(); // presumably saved from the main thread
    // update UI with new data
}

3 个答案:

答案 0 :(得分:6)

Stephen Toub在this thread中提供了有关推理的更多信息。

总而言之,出于两个原因,这不是一个好主意:

  1. 它促进了非结构化代码。如果您需要执行“繁重处理”,则应将其放在Task.Run中。更好的是,将您的业务逻辑与UI逻辑分开。
  2. 错误处理和(某些)延续在未知的上下文中运行。 catch中的finally / Test块需要处理在线程池 UI上下文中运行(如果它们在线程池上下文中运行,他们不能使用SwitchTo来跳转UI上下文。此外,只要您await返回的Task您应该没问题(await将在必要时更正延续上下文),但是如果您有明确的ContinueWith延续使用ExecuteSynchronously,然后他们会遇到与catch / finally阻止相同的问题。
  3. 简而言之,如果没有SwitchTo,代码就更清晰,更具可预测性。

答案 1 :(得分:2)

ConfigureAwait实际上比SwitchTo更危险。精神上跟踪当前上下文和最后一次SwitchTo调用并不比跟踪上次分配变量的位置困难。另一方面,当且仅当调用实际异步运行时,ConfigureAwait才会切换上下文。如果任务已完成,则保留上下文。你无法控制这一点。

答案 2 :(得分:1)

this GitHub issue中,David Fowler和Stephen Toub认为SwitchTo即将回到CLR,因为await在{ {1}} / try

IMO,显式使用catch之类的东西要比依赖第三方库代码中的await TaskScheduler.Default.SwitchTo()更好,特别是如果我们要确保该代码不在任何自定义同步上下文中执行时。我有一个博客帖子with more details on that,其中包括an experimental implementation of SwitchTo

简而言之,我相信下面的第一个选项以最少的样板代码清楚地表明了意图:

ConfigureAwait(false)
// switch to the thread pool explicitly for the rest of the async method
await TaskScheduler.Default.SwitchTo();
await RunOneWorkflowAsync();
await RunAnotherWorkflowAsync();
// execute RunOneWorkflowAsync on the thread pool 
// and stay there for the rest of the async method
await Task.Run(RunOneWorkflowAsync).ConfigureAwait(false);
await RunAnotherWorkflowAsync();
await Task.Run(async () => 
{
  // start on the thread pool
  await RunOneWorkflowAsync();
  await RunAnotherWorkflowAsync();
}).ConfigureAwait(false);
// continue on the thread pool for the rest of the async method