3

我知道TPL是面向任務的,而傳統的線程模型是面向工作者的。 任務讓你主要關注你想要解決什麼問題,而不是如何使用 來完成。但是當涉及到線程和任務關係時,我仍然有點困惑。如何確定使用TPL時將執行哪個線程?

下面是一個演示代碼:

namespace AsyncUnderTheHood 
{ 
    class Program 
    { 

     static void Main(string[] args) 
     { 
      Console.WriteLine("Main Start : {0}", Thread.CurrentThread.ManagedThreadId); 
      AwaitTest(); 
      Console.WriteLine("Main End : {0}", Thread.CurrentThread.ManagedThreadId); 
      Console.ReadLine(); 
     } 

     public static void DoWork() 
     { 
      Console.WriteLine("DoWork Start: {0}", Thread.CurrentThread.ManagedThreadId); 
      Thread.Sleep(5000); 
      Console.WriteLine("DoWork End: {0}", Thread.CurrentThread.ManagedThreadId); 
     } 

     public async static void AwaitTest() 
     { 
      Console.WriteLine("AwaitTest Start : {0}", Thread.CurrentThread.ManagedThreadId); 
      Task t = new Task(DoWork); 
      t.Start(); 
      await t; 
      Console.WriteLine("AwaitTest Done : {0}", Thread.CurrentThread.ManagedThreadId); 
     } 
    } 
} 

輸出是這樣的:

Main Start : 1 
AwaitTest Start : 1 <------------ A 
DoWork Start: 3 
Main End : 1 
DoWork End: 3 
AwaitTest Done : 3 <------------ B 

我的問題是,爲什麼A和B是在不同的線程?

相同的方法在不同的線程上執行,當線程親和性很重要時,這會導致問題嗎?

+0

您正在控制檯應用程序中使用異步無效方法..是真的好嗎? – 2013-04-07 16:33:16

+0

@WouterdeKort正如你從輸出中看到的那樣,它可以工作(因爲'ReadLine()'。雖然這是一種不好的做法,不應該在生產代碼中完成。 – svick 2013-04-07 16:35:04

+0

@WouterdeKort這是「工作」,因爲如果main沒有讓應用程序像這樣運行,你永遠無法「等待」async void方法,應用程序可能在任務開始之前退出,不建議使用async void事件處理函數以外的任何其他方法 – 2013-04-07 16:37:19

回答

1

您已要求系統「等待」任務。你真正要求的是,調用await的線程應該繼續運行,並且await之後的所有內容都是一個「延續」,當任務完成時它將異步運行。由於在控制檯應用程序中沒有「消息泵」,所以沒有簡單的方法可以重新回到「主」線程,因此繼續使用異步Task的線程繼續。如果您在WinForm或WPF應用程序中執行了相同的測試,則繼續將在UI線程上運行。

3

爲什麼A和B在不同的線程?

首先,如果你Task S被默認的調度計劃,那麼就無法保證其Thread將在Task運行。並且AwaitTest()的各個部分分開執行,因此不能保證它們將在同一個線程上運行。

其次,默認調度程序使用ThreadPool來執行Task s。每個async方法的第一部分同步運行。在你的情況下,這意味着AwaitTest()的第一部分將在主線程上運行,而第二部分將在某個線程上運行ThreadPool線程。所以,你實際上保證他們不會在同一個線程上運行。

當線程親和性很重要時,這會導致問題嗎?

它當然可以。但是,在線程親和性很重要的最常見情況下,它會正常工作:GUI編程。這是因爲GUI應用程序具有SynchronizationContext集合,這意味着如果async方法的第一部分在UI線程上運行,則第二部分也將在那裏運行(除非通過使用ConfigureAwait(false)來禁用該方法)。

但在其他情況下,它會導致問題。例如,採取以下代碼:

Monitor.Enter(lockObject); 
await someTask; 
Monitor.Exit(lockObject); 

此代碼不會在控制檯應用程序中工作(Exit()將最有可能拋SynchronizationLockException),因爲Exit()可以在不同的線程比Enter()運行。

0

This blog post對TPL如何使用多個任務隊列進行工作竊取以在現有線程池之上進行分層以獲得最佳性能有很好的描述。

相關問題