简体   繁体   English

如何创建自定义SynchronizationContext,以便我自己的单线程事件循环可以处理所有延续?

[英]How do I create a custom SynchronizationContext so that all continuations can be processed by my own single-threaded event loop?

Say you're writing a custom single threaded GUI library (or anything with an event loop). 假设您正在编写自定义单线程GUI库(或带有事件循环的任何内容)。 From my understanding, if I use async/await , or just regular TPL continuations, they will all be scheduled on TaskScheduler.Current (or on SynchronizationContext.Current ). 根据我的理解,如果我使用async/await ,或者只是常规的TPL延续,它们都将在TaskScheduler.Current (或SynchronizationContext.Current )上进行调度。

The problem is that the continuation might want to access the single threaded parts of the library, which means it has to execute in the same event loop. 问题是延续可能想要访问库的单线程部分,这意味着它必须在同一个事件循环中执行。 For example, given a simple game loop, the events might be processed like this: 例如,给定一个简单的游戏循环,事件可能会像这样处理:

// All continuation calls should be put onto this queue
Queue<Event> 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();
}

Now given my assumption is correct and TPL uses the SynchronizationContext.Current , any code in the application should be able to do something like this: 现在假设我的假设是正确的,并且TPL使用SynchronizationContext.Current ,应用程序中的任何代码都应该能够执行以下操作:

async void Foo() {
    someLabel.Text = "Processing";

    await BackgroundTask();

    // This has to execute on the main thread
    someLabel.Text = "Done";
}

Which brings me to the question. 这让我想到了这个问题。 How do I implement a custom SynchronizationContext that would allow me to handle continuations on my own thread? 如何实现允许我在自己的线程上处理延续的自定义SynchronizationContext Is this even the correct approach? 这甚至是正确的方法吗?

Implementing a custom SynchronizationContext is not the easiest thing in the world. 实现自定义SynchronizationContext并不是世界上最简单的事情。 I have an open-source single-threaded implementation here that you can use as a starting point (or possibly just use in place of your main loop). 在这里有一个开源的单线程实现,您可以将其用作起点(或者可以仅使用代替主循环)。

By default, AsyncContext.Run takes a single delegate to execute and returns when it is fully complete (since AsyncContext uses a custom SynchronizationContext , it is able to wait for async void methods as well as regular async/sync code). 默认情况下, AsyncContext.Run执行单个委托并在完全完成时返回(因为AsyncContext使用自定义SynchronizationContext ,它能够等待async void方法以及常规异步/同步代码)。

AsyncContext.Run(async () => await DoSomethingAsync());

If you want more flexibility, you can use the AsyncContext advanced members (these do not show up in IntelliSense but they are there) to keep the context alive until some external signal (like "exit frame"): 如果你想要更多的灵活性,你可以使用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 's Run and Execute replace the current SynchronizationContext while they are running, but they save the original context and set that as current before returning. AsyncContextRunExecuteRun替换当前的SynchronizationContext ,但它们保存原始上下文并在返回之前将其设置为当前。 This allows them to work nicely in a nested fashion (eg, "frames"). 这允许它们以嵌套方式(例如,“帧”)很好地工作。

(I'm assuming by "frame" you mean a kind of WPF-like dispatcher frame). (我假设“框架”你的意思是一种类似WPF的调度程序框架)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM