16

今天尝试使用SwitchTo方法切换到GUI线程,发现我举出来的例子不起作用,只是因为方法不存在。

然后我在这里找到了这个简介:

我们摆脱它的原因是因为它太危险了。另一种方法是将您的代码捆绑在 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
}
4

4 回答 4

9

Stephen Toub 在这个线程中有一些关于推理的更多信息。

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

  1. 它促进了非结构化代码。如果您有需要做的“繁重处理”,则应将其放在Task.Run. 更好的是,将您的业务逻辑与您的 UI 逻辑分开。
  2. 错误处理和(某些)延续在未知上下文中运行。catch/finallyTest需要处理在线程池UI 上下文中运行(如果它们在线程池上下文中运行,则它们不能用于SwitchTo跳转到 UI 上下文)。此外,只要你await返回Task你应该没问题(await如果需要,将更正继续上下文),但如果你有明确的ContinueWith继续使用,那么它们将遇到与/块ExecuteSynchronously相同的问题。catchfinally

简而言之,没有SwitchTo.

于 2013-03-12T14:56:58.443 回答
5

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

于 2014-02-06T22:38:54.260 回答
2

根据本期 GitHub 问题中的 David Fowler 和 Stephen Toub 的说法,现在是 2020 年,看起来SwitchTo很快就会回到 CLR,因为inside /没有更多限制了。awaittrycatch

IMO,使用类似await TaskScheduler.Default.SwitchTo()明确的东西比依赖ConfigureAwait(false)第三方库代码更好,特别是如果我们想确保代码不会在任何自定义同步上下文上执行。我有一篇博文,其中包含更多详细信息,包括.SwitchTo

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

// 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
// start on whatever the current synchronization context is
await RunOneWorkflowAsync().ConfigureAwait(false);
// continue on the thread pool for the rest of the async method,
// unless everything inside `RunOneWorkflowAsync` has completed synchronously
await RunAnotherWorkflowAsync();
于 2020-09-28T08:23:05.983 回答
0

Microsoft.VisualStudio.Threading包中提供了SwitchTo扩展方法。这是此方法的签名:

public static
    Microsoft.VisualStudio.Threading.AwaitExtensions.TaskSchedulerAwaitable
    SwitchTo(this System.Threading.Tasks.TaskScheduler scheduler,
    bool alwaysYield = false);

这是一个如何使用它的示例:

using Microsoft.VisualStudio.Threading;

private async void Button_Click(object sender, EventArgs e) 
{
    var ui = TaskScheduler.FromCurrentSynchronizationContext(); // Capture the UI thread

    // Do something on the UI thread

    await TaskScheduler.Default.SwitchTo(); // Switch to the ThreadPool

    // Do something on the ThreadPool

    await ui.SwitchTo(); // Switch back to the UI thread

    // Do something on the UI thread
}
于 2022-01-27T00:10:50.237 回答