Await, SynchronizationContext, and Console Apps: Part 2
昨天,我在博客上写了一篇关于如何实现自定义 SynchronizationContext 的博客,以便抽取异步方法使用的延续,以便可以在单个专用线程上处理它们。我还强调,这基本上是像Windows Forms和Windows Presentation Foundation这样的UI框架对他们的消息泵所做的。
现在我们了解了这些东西如何工作的机制,值得指出的是,我们可以在不编写自己的自定义 SynchronizationContext 的情况下实现相同的基本语义。相反,我们可以使用 .NET Framework 中已经存在的一个:DispatcherSynchronizationContext。通过其调度程序类和PushFrame方法,WPF 提供了“进入执行循环”以“处理挂起的工作项”的功能(如 MSDN 中所述)。这正是我们的自定义 SynchronizationContext 在使用 BlockingCollection<T> 时所做的,因此我们可以只使用 WPF 的支持,而不是开发我们自己的支持。(你可能会问,为什么我开始描述如何手动完成它,并编写我自己的方法来举例说明它。我这样做是因为我认为真正了解事物的运作方式很重要;我通常会发现,如果开发人员对幕后发生的事情有正确的心智模型,那么他们就会写出更好的高级代码,从而使他们能够更好地推理错误、性能、可靠性等。
下面您可以看到实现此支持需要多少行代码。(若要编译此代码,需要引用 WindowsBase.dll以引入相关的 WPF 类型。
public static class AsyncPump { public static void Run(Func<Task> func) { if (func == null) throw new ArgumentNullException("func"); var prevCtx = SynchronizationContext.Current; try { var syncCtx = new DispatcherSynchronizationContext(); SynchronizationContext.SetSynchronizationContext(syncCtx); var t = func(); if (t == null) throw new InvalidOperationException(); var frame = new DispatcherFrame(); t.ContinueWith(_ => { frame.Continue = false; }, TaskScheduler.Default); Dispatcher.PushFrame(frame); t.GetAwaiter().GetResult(); } finally { SynchronizationContext.SetSynchronizationContext(prevCtx); } } }
简而言之,我们实例化一个调度程序同步上下文并将其发布为当前线程上的当前线程;这使得正在处理的异步方法内部的 Wait 能够将其延续排队回此线程和此线程的调度程序。然后我们实例化一个 DispatcherFrame,它表示 WPF 消息泵的执行循环。我们使用延续向该 DispatcherFrame 发出信号,当它应该退出其循环时(即当任务完成时)。然后我们通过 PushFrame 方法启动帧的执行循环。
总而言之,只需几行代码即可实现一些强大而有用的行为。
Await, SynchronizationContext, and Console Apps: Part 2 - .NET Parallel Programming (microsoft.com)
浙公网安备 33010602011771号