2014-02-19 59 views
9

下面的代碼應該(至少在我看來)創建100個任務,這些任務都是並行等待的(這就是關於併發的觀點,對吧:D?),並且幾乎在同一時間完成。我猜想每個Task.Delay Timer對象都是在內部創建的。Task.Delay是否啓動一個新的線程?

public static async Task MainAsync() { 

    var tasks = new List<Task>(); 
    for (var i = 0; i < 100; i++) { 
     Func<Task> func = async() => { 
      await Task.Delay(1000); 
      Console.WriteLine("Instant"); 
     }; 
     tasks.Add(func()); 
    } 
    await Task.WhenAll(tasks); 
} 

public static void Main(string[] args) { 
    MainAsync().Wait(); 
} 

但是!當我在Mono運行此我得到很奇怪的行爲:

  • 的任務不會在同一時間完成,存在着巨大的延遲(大概在500-600ms)
  • 在控制檯單顯示了很多創建的線程:

加載的程序集:/Users/xxxxx/Programming/xxxxx/xxxxxxxxxx/bin/Release/xxxxx.exe

開始線程:#2

主題開始: #3

線程開始:#4

線程開始:#5

線程開始:#6

線程開始:#7

線程完成:#3 < - 顯然1000毫秒的延遲完成?

線程完成:#2 < - 顯然延遲1000ms結束?

線程開始:#8

線程開始:#9

線程開始:#10

線程開始:#11

線程開始:#12

螺紋開始:#13

......你明白了。

這實際上是一個錯誤?或者我使用庫錯了?

[編輯] 我測試使用T定製的睡眠方法:

public static async Task MainAsync() { 
     Console.WriteLine("Started"); 
     var tasks = new List<Task>(); 
     for (var i = 0; i < 100; i++) { 
      Func<Task> func = async() => { 
       await SleepFast(1000); 
       Console.WriteLine("Instant"); 
      }; 
      tasks.Add(func()); 
     } 
     await Task.WhenAll(tasks); 
     Console.WriteLine("Ready"); 
    } 

    public static Task SleepFast(int amount) { 
     var source = new TaskCompletionSource<object>(); 
     new Timer(state => { 
      var oldSrc = (TaskCompletionSource<object>)state; 
      oldSrc.SetResult(null); 
     }, source, amount, 0); 
     return source.Task; 
    } 

這個時候,瞬間完成了所有的任務。所以我認爲這是一個非常糟糕的實現或錯誤。只是FYI:我已經在.NET上測試了使用Windows 8.1的原始代碼(使用Task.Delay),並且它按預期運行(1000個任務,等待1秒並行並完成)。

所以答案是:Mono的impl。 (某些)方法並不完美。一般來說,Task.Delay不會啓動線程,甚至很多線程都不應創建多個線程。

+2

Task.Delay內部使用一個定時器,它在內部使用線程池。由於您目前正在快速創建100個任務,因此我猜測您的線程池中的線程已用完,因此會自動創建新線程。 – ken2k

+0

所以定時器沒有使用可擴展的技術來實現?我希望有一些非常有效的東西。例如在node.js中,他們使用epoll(用於linux),我相信。 – Kr0e

+0

每個任務之間500-600毫秒?你能發佈確切的時間嗎? –

回答

2

Task庫的設計更多的管理阻塞的任務不會阻塞整個流程(任務異步,容易混淆的所謂「任務並行」由Microsoft)和做併發計算(並行執行)的大塊。

任務庫使用一個調度程序和隊列作業準備好執行。作業運行時,他們會在線程池線程中這樣做,並且這些線程數量非常有限。有一個邏輯來擴展線程數量,但除非你擁有數百個CPU核心,否則它將保持較低的數量。

因此,爲了回答這個問題,您的一些任務排隊等待池中的線程,而其他延遲的任務已由調度程序發出。

調度程序和線程池邏輯可以在運行時更改,但是如果您試圖快速完成大量計算Task不適合工作。如果你想處理大量緩慢的資源(如磁盤,數據庫或互聯網資源),Task可能有助於保持應用程序的響應。

如果你只是想了解Task嘗試這些:

+0

因此使用Task庫編寫高性能服務器是不可能的? (因爲在服務器場景中,您肯定有很多小型/快速任務。) – Kr0e

+3

更像是您可以使用任務庫來確保您的服務器未被IO阻塞。任務不會解決性能問題,但是是一個有用的工具。它們更像是「綠色線程」或Erlang線程 - 它們不會讓您的硬件更快,但它們可以更好地使用可用的電源。像往常一樣優化,*首先測量* –

0

在.NET框架桌面。

總之,這個特殊的VM線程定期檢查定時器的隊列並在線程池隊列上運行定時器的委託。 Task.Delay不會創建新的Thread,但仍可能很重,並且無法保證執行順序或對截止日期的精確性。據我所知,傳遞取消Task.Delay可能最終只是從集合中刪除項目,沒有線程池工作排隊。

Task.Delay安排爲DelayPromise通過創建新的System.Threading.Timer。所有計時器都存儲在TimerQueue的AppDomain單例中。 Native VM timer用於回調.NET以檢查是否需要從隊列中觸發任何定時器。通過ThreadPool.UnsafeQueueUserWorkItem定時執行的定時器代表。