简体   繁体   English

.NET 是否在新的不同线程池线程上恢复等待继续,还是重用先前恢复的线程?

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

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

Let's image below C# code in a .NET Core console application:让我们在下面的 .NET Core 控制台应用程序中的 C# 代码中想象一下:

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();
        }
    }
}

It will output:它会输出:

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...

I have tried to add ConfigureAwait(false) after each await Task , but it will get the same result.我试图在每个 await Task之后添加ConfigureAwait(false) ,但它会得到相同的结果。

As we can see, it seems like all await continuations reused the thread created in Task.Run of AsyncThree() method.正如我们所见,似乎所有的 await 延续都重用了在AsyncThree()方法的Task.Run中创建的线程。 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?我想问一下.NET是否会一直在之前的恢复线程上恢复等待继续,或者在某些情况下它会从线程池中应用一个新的不同线程?

I knew there is answer the continuation will resume on a thread pool thread in below discussion:我知道有答案,继续将在下面的讨论中的线程池线程上恢复:

async/await. 异步/等待。 Where is continuation of awaitable part of method performed? 在哪里执行方法的可等待部分的继续?

Let's exclude the SynchronizationContext case in above link, since we are now discussing a .NET console application.让我们排除上面链接中的SynchronizationContext情况,因为我们现在正在讨论 .NET 控制台应用程序。 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?但是我想问一下,我的例子中的线程池线程似乎总是thread id 4 ,我不知道是不是因为thread id 4在线程池中总是空闲的,所以每个延续都巧合地重用它,或者.NET有机制会尽量复用之前的恢复线程吗?

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...

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

Neither.两者都不。 By default, when await ing Task s, await will capture a "context" and use that to resume the asynchronous method .默认情况下,当await Taskawait将捕获“上下文”并使用它来恢复异步方法 This "context" is SynchronizationContext.Current , unless it is null , in which case the context is TaskScheduler.Current .这个“上下文”是SynchronizationContext.Current ,除非它是null ,在这种情况下上下文是TaskScheduler.Current In your example code, the context is the thread pool context.在您的示例代码中,上下文是线程池上下文。

The other part of the puzzle is undocumented: await uses the TaskContinuationOptions.ExecuteSynchronously flag .谜题的另一部分没有记录: await使用TaskContinuationOptions.ExecuteSynchronously标志 This means that when the Task.Run task is completed (by thread 4 ), its continuations are run immediately and synchronously - if possible .这意味着当Task.Run任务完成时(由线程4完成),它的延续会立即同步运行 - 如果可能的话 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.在您的示例代码中,延续可能会同步运行,因为线程4上有足够的堆栈,并且延续应该在线程池线程上运行,而线程4是线程池线程。

Likewise, when AsyncThree completes, the continuation for AsyncTwo is run immediately and synchronously - again on thread 4 since it meets all the criteria.同样,当AsyncThree完成时, AsyncTwo的延续会立即同步运行 - 再次在线程4上运行,因为它满足所有条件。

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 (eg, a db read) that completes the entire chain and sends the response.这是一种优化,在像 ASP.NET 这样的场景中特别有用,在这些场景中,通常有一系列async方法并完成一个任务(例如,读取数据库)来完成整个链并发送响应。 In those cases you want to avoid unnecessary thread switches.在这些情况下,您希望避免不必要的线程切换。

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.一个有趣的副作用是你最终得到了一个“反向调用堆栈”:线程池线程4运行了你的代码,然后完成了AsyncThree ,然后是AsyncTwoAsyncOne ,并且这些完成中的每一个都在实际调用中堆。 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 .如果您在AsyncOneWriteLine上放置断点(并查看外部代码),您可以看到ThreadPoolWorkQueue.Dispatch (间接)调用AsyncThree (间接)调用AsyncTwo (间接)调用AsyncOne

As you can see Why is the initial thread not used on the code after the awaited method?正如您所看到的, 为什么在等待方法之后的代码上没有使用初始线程? it is quite possible to resume on another thread, based on what is available at the moment.根据目前可用的内容,很有可能在另一个线程上恢复。

In asynchronous programming there is not definite usage of specific threads when used with async await.在异步编程中,当与 async await 一起使用时,没有明确使用特定线程。 You only know that an available thread will be picked from the thread pool.您只知道将从线程池中挑选一个可用线程。

In your case, since the execution is pretty much sequential, the thread is freed and you get the number 4.在您的情况下,由于执行几乎是连续的,线程被释放并且您得到数字 4。

Based on the thread pool documentation https://docs.microsoft.com/en-us/dotnet/standard/threading/the-managed-thread-pool the thread pool is unique per process, so I'd expect the usage of the first available thread to be used.基于线程池文档https://docs.microsoft.com/en-us/dotnet/standard/threading/the-managed-thread-pool每个进程的线程池是唯一的,所以我希望使用要使用的第一个可用线程。 So is you have no other concurrent operations, the thread 4 will be reused each time.所以当你没有其他并发操作时,线程 4 每次都会被重用。 There are no guarantees though.但是没有任何保证。

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

Most of the time it will use the same thread, but this is not guaranteed.大多数时候它将使用相同的线程,但这不能保证。 This answer to a question where the OP wants to force it to the same thread gives some details. This answer to a question where OP 希望强制它到同一线程提供了一些细节。

Where the continuation is run is up to the TaskScheduler .继续运行的位置取决于TaskScheduler I haven't looked, but I imagine it will run the continuation on the same thread just to avoid unnecessary overhead when working with the thread pool.我没有看过,但我想它会在同一个线程上运行延续,只是为了避免在使用线程池时不必要的开销。

Is there any possibility each continuation will resume on a different thread pool thread like below?每个延续是否有可能在不同的线程池线程上恢复,如下所示?

Yes, there's a possibility, but probable that you won't see this, again because of the scheduler.是的,有一种可能性,但很可能你不会看到这个,同样是因为调度程序。 You'd probably have to write your own to force a difference, and personally I don't know why you'd do that.您可能必须自己编写以强制改变,而我个人不知道您为什么要这样做。 (Not saying that was your intent). (不是说这是你的意图)。

There is no real async call in your sample code ie.您的示例代码中没有真正的异步调用,即。 An i/o call so most likely the continuation is inlined on the original thread as an optimization.一个 i/o 调用,因此很可能将延续作为优化内联在原始线程上。 In your async methods if you add await Task.Delay you may observe continuation may run on a different thread.在您的异步方法中,如果您添加 await Task.Delay,您可能会观察到延续可能在不同的线程上运行。 Bottomline never make any assumptions on which thread the continuations run, assume it gets run on another thread.底线永远不会对延续运行的线程做出任何假设,假设它在另一个线程上运行。

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

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