.NET是在新的不同线程池线程上恢复等待继续还是重新使用先前恢复的线程? [英] Does .NET resume an await continuation on a new different thread pool thread or reuse the thread from a previous resumption?

查看:108
本文介绍了.NET是在新的不同线程池线程上恢复等待继续还是重新使用先前恢复的线程?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

.NET是在新的不同线程池线程上恢复等待继续还是在以前的恢复中重用该线程?

Does .NET resume an await continuation on a new different thread pool thread or reuse the thread from a previous resumption?

.NET Core控制台应用程序中C#代码下方的图像:

Let's image below C# code in a .NET Core console application:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace NetCoreResume
{
    class Program
    {
        static async Task AsyncThree()
        {
            await Task.Run(() =>
            {
                Console.WriteLine($"AsyncThree Task.Run thread id:{Thread.CurrentThread.ManagedThreadId.ToString()}");
            });

            Console.WriteLine($"AsyncThree continuation thread id:{Thread.CurrentThread.ManagedThreadId.ToString()}");
        }

        static async Task AsyncTwo()
        {
            await AsyncThree();

            Console.WriteLine($"AsyncTwo continuation thread id:{Thread.CurrentThread.ManagedThreadId.ToString()}");
        }

        static async Task AsyncOne()
        {
            await AsyncTwo();

            Console.WriteLine($"AsyncOne continuation thread id:{Thread.CurrentThread.ManagedThreadId.ToString()}");
        }

        static void Main(string[] args)
        {
            AsyncOne().Wait();

            Console.WriteLine("Press any key to end...");
            Console.ReadKey();
        }
    }
}

它将输出:

AsyncThree Task.Run thread id:4
AsyncThree continuation thread id:4
AsyncTwo continuation thread id:4
AsyncOne continuation thread id:4
Press any key to end...

我尝试在每次等待Task之后添加ConfigureAwait(false),但是会得到相同的结果.

I have tried to add ConfigureAwait(false) after each await Task, but it will get the same result.

似乎所有等待继续都重复使用了AsyncThree()方法的Task.Run中创建的线程. 我想问一问.NET是否将始终在先前的恢复线程上恢复等待继续,还是在某些情况下将应用与线程池不同的新线程?

As we can see, it seems like all await continuations reused the thread created in Task.Run of AsyncThree() method. I want to ask if .NET will always resume the await continuation on previous resumption thread, or it will apply a new different thread from thread pool in some occasions?

我知道有答案,继续将在下面的讨论中的线程池线程上恢复:

I knew there is answer the continuation will resume on a thread pool thread in below discussion:

async/await.在哪里执行方法的等待部分?

让我们排除以上链接中的SynchronizationContext情况,因为我们现在正在讨论.NET控制台应用程序.但我想问一下,在示例中线程池线程始终是thread id 4,我不知道是否是因为thread id 4在线程池中始终是空闲的,所以每个连续性是通过巧合重用的,还是.NET具有机制将尽可能多地重用先前的恢复线程?

Let's exclude the SynchronizationContext case in above link, since we are now discussing a .NET console application. But I want to ask it seems like that thread pool thread in my example is always thread id 4, I don't know whether it is because thread id 4 is always free in the thread pool, so every continuation reuse it by coincidence, or .NET has mechanism will reuse the previous resumption thread as much as possible?

每个延续是否有可能在如下所示的不同线程池线程上恢复?

Is there any possibility each continuation will resume on a different thread pool thread like below?

AsyncThree Task.Run thread id:4
AsyncThree continuation thread id:5
AsyncTwo continuation thread id:6
AsyncOne continuation thread id:7
Press any key to end...

推荐答案

.NET是在新的不同线程池线程上恢复等待继续还是在以前的恢复中重用该线程?

Does .NET resume an await continuation on a new different thread pool thread or reuse the thread from a previous resumption?

都不是.默认情况下,当await设置Task时, await将捕获一个上下文",并使用它来恢复异步方法.除非是null,否则此上下文"为SynchronizationContext.Current,在这种情况下,上下文为TaskScheduler.Current.在您的示例代码中,上下文是线程池上下文.

Neither. By default, when awaiting Tasks, await will capture a "context" and use that to resume the asynchronous method. This "context" is SynchronizationContext.Current, unless it is null, in which case the context is TaskScheduler.Current. In your example code, the context is the thread pool context.

难题的另一部分未记录在案: await使用TaskContinuationOptions.ExecuteSynchronously标志.这意味着Task.Run任务完成(通过线程4)时,其继续将立即并同步运行-

The other part of the puzzle is undocumented: await uses the TaskContinuationOptions.ExecuteSynchronously flag. This means that when the Task.Run task is completed (by thread 4), its continuations are run immediately and synchronously - if possible. In your example code, the continuation may run synchronously because there's enough stack on thread 4 and the continuation should be run on a thread pool thread and thread 4 is a thread pool thread.

同样,当AsyncThree完成时,AsyncTwo的延续立即并同步运行-再次在线程<​​c16>上运行,因为它满足所有条件.

Likewise, when AsyncThree completes, the continuation for AsyncTwo is run immediately and synchronously - again on thread 4 since it meets all the criteria.

此优化在ASP.NET之类的方案中特别有用,在该方案中,通常有一系列async方法并完成一个任务(例如db读取)来完成整个链并发送该任务.回复.在这种情况下,您要避免不必要的线程切换.

This is an optimization that is especially helpful in scenarios like ASP.NET, where it's common to have a chain of async methods and have one task completing (e.g., a db read) that completes the entire chain and sends the response. In those cases you want to avoid unnecessary thread switches.

此操作的一个有趣的副作用是,您最终得到了各种反向调用堆栈":线程池线程4运行了代码,然后先完成了AsyncThree然后是AsyncTwo然后是AsyncOne ,并且每个完成都在实际的调用堆栈中.如果在AsyncOne中的WriteLine上放置一个断点(并查看外部代码),则可以看到ThreadPoolWorkQueue.Dispatch(间接)称为AsyncThree的位置,(间接)称为AsyncTwo的位置(间接)称为AsyncOne.

An interesting side effect of this is that you end up with an "inverted call stack" of sorts: the thread pool thread 4 ran your code and then completed AsyncThree and then AsyncTwo and then AsyncOne, and each of those completions are on the actual call stack. If you place a breakpoint on the WriteLine in AsyncOne (and look at external code), you can see where ThreadPoolWorkQueue.Dispatch (indirectly) called AsyncThree which (indirectly) called AsyncTwo which (indirectly) called AsyncOne.

这篇关于.NET是在新的不同线程池线程上恢复等待继续还是重新使用先前恢复的线程?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆