简体   繁体   English

为什么在使用多个等待时 SynchronizationContext.Post() 只被调用一次?

[英]Why does SynchronizationContext.Post() get called only once when using multiple awaits?

Consider the following example:考虑以下示例:

async Task DoWork()
{
    await Task.Run(() =>
    {
        for (int i = 0; i < 25; i++)
        {
            Console.WriteLine("Task run 1: " + Thread.CurrentThread.ManagedThreadId);
        }
    });

    // The SynchronizationContext.Post() gets called after Run 1 and before Run 2

    await Task.Run(() =>
    {
        for (int i = 0; i < 25; i++)
        {
            Console.WriteLine("Task run 2: " + Thread.CurrentThread.ManagedThreadId);
        }
    });

    // I expect it to run after Run 2 and before Run 3 as well but it doesn't

    await Task.Run(() =>
    {
        for (int i = 0; i < 25; i++)
        {
            Console.WriteLine("Task run 3: " + Thread.CurrentThread.ManagedThreadId);
        }
    });
}

I would expect a call to SynchronizationContext.Post() to be made every time an await operation ends but after overriding the Post() like this我希望每次等待操作结束时都会调用SynchronizationContext.Post()但在像这样覆盖Post()之后

public class MySynchronizationContext
{
  public override void Post(SendOrPostCallback d, object? state)
  {
      Console.WriteLine("Continuation: " + Thread.CurrentThread.ManagedThreadId);
      base.Post(d, state);
  }
}

Installed like this at the very start of Main()Main()的一开始就这样安装

SynchronizationContext.SetSynchronizationContext(new MySynchronizationContext());

It only prints the message once, after the first Run() is completed.在第一个Run()完成后,它只打印一次消息。

I assumed that's because Task.Run() may detect that it's being called on a threadpool thread and just reuse the current thread but that seems not to be the case because some of my tests resulted in Run 2 and Run 3 running on different threads.我假设那是因为Task.Run()可能检测到它正在线程池线程上被调用并且只是重用当前线程,但情况似乎并非如此,因为我的一些测试导致运行 2运行 3在不同的线程上运行。

Why does the completion of an await ed Task only runs after the first await ?为什么await ed Task 的完成仅在第一个await之后运行?

The SynchronizationContext.SetSynchronizationContext method installs the supplied SynchronizationContext on the current thread. SynchronizationContext.SetSynchronizationContext方法在当前线程上安装提供的SynchronizationContext In order for the same SynchronizationContext to be captured and reused by subsequent await s, the implementation of the SynchronizationContext must ensure that the continuation is invoked on the original thread, or that it installs itself on any other thread that it uses for invoking the continuation.为了让相同的SynchronizationContext被后续的await捕获和重用, SynchronizationContext的实现必须确保在原始线程上调用延续,或者它在用于调用延续的任何其他线程上安装自身。

Your implementation ( MySynchronizationContext ) doesn't do that.您的实现 ( MySynchronizationContext ) 不会那样做。 It just delegates the Post call to the base.Post , which invokes the continuation on the ThreadPool .它只是将Post调用委托给base.Post ,后者调用ThreadPool上的延续。 The MySynchronizationContext instance is not installed on any of the ThreadPool threads, so the second await finds nothing to capture, and so the second continuation is invoked on whatever thread the Task.Run method completed, which is also a ThreadPool thread. MySynchronizationContext实例未安装在任何ThreadPool线程上,因此第二个await找不到要捕获的内容,因此在Task.Run方法完成的任何线程上调用第二个延续,这也是一个ThreadPool线程。 So essentially you get the same behavior that you would get by using a properly implemented SynchronizationContext , like Stephen Cleary's AsyncContext , and configuring the first await with ConfigureAwait(false) .因此,从本质上讲,您获得的行为与使用正确实现的SynchronizationContext (如 Stephen Cleary 的AsyncContext )并使用ConfigureAwait(false)配置第一个await所获得的行为相同。

I ended up figuring it out on my own.我最终自己弄清楚了。

The problem seemed to be my invalid understanding of capturing current SynchronizationContext by the await .问题似乎是我对await捕获当前SynchronizationContext的无效理解。

async Task DoWork()
{
    // This is still in the main thread so SynchronizationContext.Current
    // returns an instance of MySynchronizationContext which this
    // await captures.
    await Task.Run(() =>
    {
        for (int i = 0; i < 25; i++)
        {
            Console.WriteLine("Task run 1: " + Thread.CurrentThread.ManagedThreadId);
        }
    });
    // Here it uses the captured MySynchronizationContext to call
    // the .Post() method. The message gets printed to the console and
    // continuation gets put on the ThreadPool

    // This await tries to capture current SynchronizationContext but
    // since we're on the ThreadPool's thread, SynchronizationContext.Current
    // returns null and it uses the default implementation
    // instead of MySynchronizationContext. This is why my message from
    // the overriden .Post() doesn't get printed which made me believe
    // that it didn't call .Post() at all. It did, just not my .Post()
    await Task.Run(() =>
    {
        for (int i = 0; i < 25; i++)
        {
            Console.WriteLine("Task run 2: " + Thread.CurrentThread.ManagedThreadId);
        }
    });
    // .Post() gets called on the default SynchronizationContext

    // Again, we're on the ThreadPool's thread,
    // so the default SynchronizationContext gets captured
    await Task.Run(() =>
    {
        for (int i = 0; i < 25; i++)
        {
            Console.WriteLine("Task run 3: " + Thread.CurrentThread.ManagedThreadId);
        }
    });
}

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

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