2015-08-22 180 views
13

雖然試圖找出新的(可能不是現在這麼新,但對我來說是新手)Task在C#中的異步編程,我遇到了一個問題,讓我花了一些時間弄清楚,我不知道爲什麼。Task.WaitAll不等待任務完成

我已經解決了這個問題,但我仍然不確定爲什麼它是一個問題開始。我只是想我會分享我的經驗,以防萬一有人遇到同樣的情況。

如果任何大師想告訴我問題的原因,這將是美好的,非常感激。我總是喜歡只知道爲什麼東西不起作用!

我做了一個測試任務,如下所示:

Random rng = new Random((int)DateTime.UtcNow.Ticks); 
int delay = rng.Next(1500, 15000); 
Task<Task<object>> testTask = Task.Factory.StartNew<Task<object>>(
    async (obj) => 
     { 
      DateTime startTime = DateTime.Now; 
      Console.WriteLine("{0} - Starting test task with delay of {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (int)obj); 
      await Task.Delay((int)obj); 
      Console.WriteLine("{0} - Test task finished after {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (DateTime.Now - startTime).TotalMilliseconds); 
      return obj; 
     }, 
     delay 
    ); 
Task<Task<object>>[] tasks = new Task<Task<object>>[] { testTask }; 

Task.WaitAll(tasks); 
Console.WriteLine("{0} - Finished waiting.", DateTime.Now.ToString("h:mm:ss.ffff")); 

// make console stay open till user presses enter 
Console.ReadLine(); 

然後我跑的應用程序,看看它吐出來。以下是一些示例輸出:

6:06:15.5661 - 延遲3053ms開始測試任務。
6:06:15.5662 - 已完成等待。
6:06:18.5743 - 3063.235ms後完成測試任務。

正如你所看到的,Task.WaitAll(tasks);聲明並沒有太多的作用。在繼續執行之前,它總共等待1毫秒。

我已經在下面回答了我自己的「問題」 - 但正如我上面所說的 - 如果有人比我更懂得解釋爲什麼這不起作用,請做! (I 認爲它可能與該方法的執行「跳出」有關,一旦它達到了await操作員 - 然後一旦等待完成後又退回......但我可能是錯的)

+0

爲什麼要那樣做'新的隨機((int)的DateTime.UtcNow.Ticks)'?爲什麼不只是'新的Random()',因爲它實際上是同一件事情。 – Enigmativity

+0

不是說這個問題對於這個問題是重要的或者相關的,而是純粹爲了看看它做了什麼,如果有的話。我沒有注意到與調用無參數構造函數有什麼不同,所以我從現在開始就這樣做。 如果你不嘗試新的東西,你不會學到任何東西。編程對我來說是一種愛好,除了Karel Robot,Pascal和SQL之外,我還沒有接受過任何正式的教育,而這已經是13年以前的事了,所以對我來說修改,嘗試,破壞事物,圖他們出去學習。 – cjk84

+0

你最好下載一個免費的.NET反編譯器並查看源代碼。 – Enigmativity

回答

17

您應該避免使用Task.Factory.StartNew與異步的await。您應該使用Task.Run

異步方法返回一個Task<T>,異步委託也可以。 Task.Factory.StartNew也返回一個Task<T>,其結果是委託參數的結果。所以當它們一起使用時,它會返回一個Task<Task<T>>>

這個Task<Task<T>>所做的就是執行委託,直到有一個任務返回,即第一個等待達到的時候。如果你只等待完成這個任務,你不會等待整個方法,只是第一個等待之前的部分。

您可以修復使用Task.Unwrap它創建了一個Task<T>,表示Task<Task<T>>>

Task<Task> wrapperTask = Task.Factory.StartNew(...); 
Task actualTask = wrapperTask.Unwrap(); 
Task.WaitAll(actualTask); 
+2

謝謝你的解釋,這是有道理的。我已將此標記爲正確答案! – cjk84

+1

@ cjk84肯定..任何時候。 – i3arnon

+1

對您來說還有一個問題:我在猜測是因爲我刪除了異步/等待並因此任務從任務>轉到任務,它純粹是因爲這個原因,並且從Task.Delay更改爲Thread。睡眠與它無關。是對的嗎? – cjk84

0

經過多次調整和拉毛後,我終於決定擺脫異步lambda,並使用System.Threading.Thread.Sleep方法,看看是否有任何區別。

新的代碼弄成這個樣子:

Random rng = new Random((int)DateTime.UtcNow.Ticks); 
int delay = rng.Next(1500, 15000); 
Task<object> testTask = Task.Factory.StartNew<object>(
    (obj) => 
     { 
      DateTime startTime = DateTime.Now; 
      Console.WriteLine("{0} - Starting test task with delay of {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (int)obj); 
      System.Threading.Thread.Sleep((int)obj); 
      Console.WriteLine("{0} - Test task finished after {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (DateTime.Now - startTime).TotalMilliseconds); 
      return obj; 
     }, 
     delay 
    ); 
Task<object>[] tasks = new Task<object>[] { testTask }; 

Task.WaitAll(tasks); 
Console.WriteLine("{0} - Finished waiting.", DateTime.Now.ToString("h:mm:ss.ffff")); 

// make console stay open till user presses enter 
Console.ReadLine(); 

注:由於去除拉姆達方法async關鍵字,任務類型就可以方便Task<object>而非Task<Task<object>> - 你可以看到這種變化上面的代碼。

而且,瞧!有效!我得到了'已完成的等待'。任務完成後的消息。

唉,我記得在你的Task代碼中讀到的地方不應該使用System.Threading.Thread.Sleep()。不記得爲什麼;但因爲它只是用於測試,而大多數任務實際上會做,而不是假裝要做的事情需要時間,這應該不是問題。

希望這可以幫助一些人。我絕對不是世界上最好的程序員(甚至關閉),我的代碼可能不是很好,但是如果它對別人有幫助,太棒了! :)

感謝您的閱讀。

編輯:我的問題的其他答案解釋爲什麼我有問題,我做了這個答案只解決了錯誤的問題。改爲Thread.Sleep(x)沒有效果。謝謝所有回答並幫助我的人!

3

這裏,Task.WaitAll等待外部任務而不是內部任務。使用Task.Run沒有嵌套的任務。這是最佳實踐解決方案。另一個解決方案是等待內在的任務。例如:

Task<object> testTask = Task.Factory.StartNew(
    async (obj) => 
     { 
      ... 
     } 
    ).Unwrap(); 

或者:

testTask.Wait(); 
testTask.Result.Wait(); 
+0

啊,我想知道爲什麼它不會讓我添加async關鍵字,直到我嵌套任務。謝謝你的信息! – cjk84

6

的問題與您的代碼是有在作怪兩個任務。一個是你的Task.Factory.StartNew調用的結果,它導致匿名函數在線程池上執行。但是,您的匿名函數又被編譯爲產生一個嵌套的任務,表示完成其異步操作。當您在Task<Task<object>>上等待時,您只需等待外部任務。要等在內的任務,你應該使用Task.Run代替Task.Factory.StartNew,因爲它會自動解開你內心的任務:

Random rng = new Random((int)DateTime.UtcNow.Ticks); 
int delay = rng.Next(1500, 15000); 
Task<int> testTask = Task.Run(
    async() => 
    { 
     DateTime startTime = DateTime.Now; 
     Console.WriteLine("{0} - Starting test task with delay of {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), delay); 
     await Task.Delay(delay); 
     Console.WriteLine("{0} - Test task finished after {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (DateTime.Now - startTime).TotalMilliseconds); 
     return delay; 
    }); 
Task<int>[] tasks = new[] { testTask }; 

Task.WaitAll(tasks); 
Console.WriteLine("{0} - Finished waiting.", DateTime.Now.ToString("h:mm:ss.ffff")); 

// make console stay open till user presses enter 
Console.ReadLine(); 
+0

謝謝你的回答! – cjk84