为什么是“SwitchTo"从异步CTP / Release中删除
本文关键字:CTP 异步 Release 删除 SwitchTo quot 为什么 | 更新日期: 2023-09-27 18:03:43
我今天尝试使用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
}
Stephen Toub在这个线程中有一些关于推理的更多信息。
总而言之,这不是一个好主意,原因有两个:
- 它提倡非结构化代码。如果您需要进行"繁重的处理",则应该将其放在
Task.Run
中。更好的是,将你的业务逻辑和你的UI逻辑分开。 - 错误处理和(某些)延续在未知上下文中运行。
catch
/finally
块在Test
将需要处理在线程池或 UI上下文中运行(如果他们在线程池上下文中运行,他们不能使用SwitchTo
跳到UI上下文中)。此外,只要您await
返回的Task
,您应该是OK的(await
将在必要时纠正延续上下文),但是如果您使用ExecuteSynchronously
显式ContinueWith
延续,那么它们将有与catch
/finally
块相同的问题。
简而言之,没有SwitchTo
,代码更干净,更可预测。
ConfigureAwait实际上比SwitchTo更危险。在心里跟踪当前上下文和最后一次SwitchTo调用并不比跟踪变量最后一次赋值的位置更困难。另一方面,当且仅当调用实际异步运行时,ConfigureAwait切换上下文。如果任务已经完成,则保留上下文。这是你无法控制的
现在是2020年,看起来SwitchTo
很快就会回到CLR,根据David Fowler和Stephen Toub在这个GitHub问题,因为在try
/catch
中没有更多的await
限制。
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();
SwitchTo
扩展方法在Microsoft.VisualStudio.Threading包中可用。下面是这个方法的签名:
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
}