簡體   English   中英

.NET 是否在新的不同線程池線程上恢復等待繼續,還是重用先前恢復的線程?

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

.NET 是否在新的不同線程池線程上恢復等待繼續,還是重用先前恢復的線程?

讓我們在下面的 .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();
        }
    }
}

它會輸出:

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

我試圖在每個 await Task之后添加ConfigureAwait(false) ,但它會得到相同的結果。

正如我們所見,似乎所有的 await 延續都重用了在AsyncThree()方法的Task.Run中創建的線程。 我想問一下.NET是否會一直在之前的恢復線程上恢復等待繼續,或者在某些情況下它會從線程池中應用一個新的不同線程?

我知道有答案,繼續將在下面的討論中的線程池線程上恢復:

異步/等待。 在哪里執行方法的可等待部分的繼續?

讓我們排除上面鏈接中的SynchronizationContext情況,因為我們現在正在討論 .NET 控制台應用程序。 但是我想問一下,我的例子中的線程池線程似乎總是thread id 4 ,我不知道是不是因為thread id 4在線程池中總是空閑的,所以每個延續都巧合地重用它,或者.NET有機制會盡量復用之前的恢復線程嗎?

每個延續是否有可能在不同的線程池線程上恢復,如下所示?

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 是否在新的不同線程池線程上恢復等待繼續,還是重用先前恢復的線程?

兩者都不。 默認情況下,當await Taskawait將捕獲“上下文”並使用它來恢復異步方法 這個“上下文”是SynchronizationContext.Current ,除非它是null ,在這種情況下上下文是TaskScheduler.Current 在您的示例代碼中,上下文是線程池上下文。

謎題的另一部分沒有記錄: await使用TaskContinuationOptions.ExecuteSynchronously標志 這意味着當Task.Run任務完成時(由線程4完成),它的延續會立即同步運行 - 如果可能的話 在您的示例代碼中,延續可能會同步運行,因為線程4上有足夠的堆棧,並且延續應該在線程池線程上運行,而線程4是線程池線程。

同樣,當AsyncThree完成時, AsyncTwo的延續會立即同步運行 - 再次在線程4上運行,因為它滿足所有條件。

這是一種優化,在像 ASP.NET 這樣的場景中特別有用,在這些場景中,通常有一系列async方法並完成一個任務(例如,讀取數據庫)來完成整個鏈並發送響應。 在這些情況下,您希望避免不必要的線程切換。

一個有趣的副作用是你最終得到了一個“反向調用堆棧”:線程池線程4運行了你的代碼,然后完成了AsyncThree ,然后是AsyncTwoAsyncOne ,並且這些完成中的每一個都在實際調用中堆。 如果您在AsyncOneWriteLine上放置斷點(並查看外部代碼),您可以看到ThreadPoolWorkQueue.Dispatch (間接)調用AsyncThree (間接)調用AsyncTwo (間接)調用AsyncOne

正如您所看到的, 為什么在等待方法之后的代碼上沒有使用初始線程? 根據目前可用的內容,很有可能在另一個線程上恢復。

在異步編程中,當與 async await 一起使用時,沒有明確使用特定線程。 您只知道將從線程池中挑選一個可用線程。

在您的情況下,由於執行幾乎是連續的,線程被釋放並且您得到數字 4。

基於線程池文檔https://docs.microsoft.com/en-us/dotnet/standard/threading/the-managed-thread-pool每個進程的線程池是唯一的,所以我希望使用要使用的第一個可用線程。 所以當你沒有其他並發操作時,線程 4 每次都會被重用。 但是沒有任何保證。

.NET 是否在新的不同線程池線程上恢復等待繼續,還是重用先前恢復的線程?

大多數時候它將使用相同的線程,但這不能保證。 This answer to a question where OP 希望強制它到同一線程提供了一些細節。

繼續運行的位置取決於TaskScheduler 我沒有看過,但我想它會在同一個線程上運行延續,只是為了避免在使用線程池時不必要的開銷。

每個延續是否有可能在不同的線程池線程上恢復,如下所示?

是的,有一種可能性,但很可能你不會看到這個,同樣是因為調度程序。 您可能必須自己編寫以強制改變,而我個人不知道您為什么要這樣做。 (不是說這是你的意圖)。

您的示例代碼中沒有真正的異步調用,即。 一個 i/o 調用,因此很可能將延續作為優化內聯在原始線程上。 在您的異步方法中,如果您添加 await Task.Delay,您可能會觀察到延續可能在不同的線程上運行。 底線永遠不會對延續運行的線程做出任何假設,假設它在另一個線程上運行。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM