如何创建自定义SynchronizationContext,以便我自己的单线程事件循环可以处理所有延续?
假设您正在编写自定义单线程GUI库(或带有事件循环的任何内容)。 根据我的理解,如果我使用async/await
,或只是常规的TPL延续,它们都将在TaskScheduler.Current
(或SynchronizationContext.Current
)上进行调度。
问题是延续可能想要访问库的单线程部分,这意味着它必须在同一个事件循环中执行。 例如,给定一个简单的游戏循环,事件可能会像这样处理:
// All continuation calls should be put onto this queue Queue events; // The main thread calls the `Update` method continuously on each "frame" void Update() { // All accumulated events are processed in order and the queue is cleared foreach (var event : events) Process(event); events.Clear(); }
现在假设我的假设是正确的,TPL使用SynchronizationContext.Current
,应用程序中的任何代码都应该能够执行以下操作:
async void Foo() { someLabel.Text = "Processing"; await BackgroundTask(); // This has to execute on the main thread someLabel.Text = "Done"; }
这让我想到了这个问题。 如何实现允许我在自己的线程上处理延续的自定义SynchronizationContext
? 这甚至是正确的方法吗?
实现自定义SynchronizationContext
并不是世界上最简单的事情。 我在这里有一个开源的单线程实现,您可以将其用作起点(或者可以仅使用代替主循环)。
默认情况下, AsyncContext.Run
执行单个委托并在完全完成时返回(因为AsyncContext
使用自定义SynchronizationContext
,它能够等待async void
方法以及常规异步/同步代码)。
AsyncContext.Run(async () => await DoSomethingAsync());
如果你想要更多的灵活性,你可以使用AsyncContext
高级成员(这些成员不会出现在IntelliSense中,但它们在那里),以保持上下文存活,直到某些外部信号(如“退出帧”):
using (var context = new AsyncContext()) { // Ensure the context doesn't exit until we say so. context.SynchronizationContext.OperationStarted(); // TODO: set up the "exit frame" signal to call `context.SynchronizationContext.OperationCompleted()` // (note that from within the context, you can alternatively call `SynchronizationContext.Current.OperationCompleted()` // Optional: queue any work you want using `context.Factory`. // Run the context; this only returns after all work queued to this context has completed and the "exit frame" signal is triggered. context.Execute(); }
AsyncContext
的Run
和Execute
在Run
替换当前的SynchronizationContext
,但它们保存原始上下文并在返回之前将其设置为当前。 这允许它们以嵌套方式(例如,“帧”)很好地工作。
(我假设“框架”你的意思是一种类似WPF的调度程序框架)。