2013-07-09 41 views
2

我有以下簡單的控制檯應用程序:異步/ AWAIT線程化的好奇心

class Program 
{ 
    private static int times = 0; 

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

     var task = DoSomething(); 
     task.Wait(); 

     Console.WriteLine("End {0}", Thread.CurrentThread.ManagedThreadId); 

     Console.ReadLine(); 
    } 

    static async Task<bool> DoSomething() 
    { 
     times++; 

     if (times >= 3) 
     { 
      return true; 
     } 

     Console.WriteLine("DoSomething-1 sleeping {0}", Thread.CurrentThread.ManagedThreadId); 
     await Task.Run(() => 
     { 
      Console.WriteLine("DoSomething-1 sleep {0}", Thread.CurrentThread.ManagedThreadId); 
      Task.Yield(); 
     }); 
     Console.WriteLine("DoSomething-1 awake {0}", Thread.CurrentThread.ManagedThreadId); 

     Console.WriteLine("DoSomething-2 sleeping {0}", Thread.CurrentThread.ManagedThreadId); 
     await Task.Run(() => 
     { 
      Console.WriteLine("DoSomething-2 sleep {0}", Thread.CurrentThread.ManagedThreadId); 
      Task.Yield(); 
     }); 
     Console.WriteLine("DoSomething-2 awake {0}", Thread.CurrentThread.ManagedThreadId); 

     bool b = await DoSomething(); 
     return b; 
    } 
} 

與輸出

Start 1 
DoSomething-1 sleeping 1 
DoSomething-1 sleep 3 
DoSomething-1 awake 4 
DoSomething-2 sleeping 4 
DoSomething-2 sleep 4 
DoSomething-2 awake 4 
DoSomething-1 sleeping 4 
DoSomething-1 sleep 3 
DoSomething-1 awake 3 
DoSomething-2 sleeping 3 
DoSomething-2 sleep 3 
DoSomething-2 awake 3 
End 1 

我知道,控制檯應用程序不提供這樣的SynchronizationContext運行任務在線程池中。但是令我驚訝的是,當從DoSomething的等待中恢復執行時,我們就和我們在等待中一樣。我曾假設,當我們恢復等待方法的執行時,我們會返回到我們等待的線程或完全在另一個線程中。

有誰知道爲什麼?我的例子有什麼缺陷?

回答

4

此行爲是由於優化(這是實施細節)。

具體而言,await計劃的延續使用TaskContinuationOptions.ExecuteSynchronously標誌。這在任何地方都沒有正式記錄,但幾個月前我確實遇到過這種情況,並且wrote it up on my blog

Stephen Toub有一個blog post that is the best documentation on how ExecuteSynchronously actually works。重要的一點是,如果該延續的任務調度程序與當前線程不兼容,ExecuteSynchronously將不會實際同步執行。

正如你指出的那樣,控制檯應用程序沒有SynchronizationContext,因此通過await計劃任務的延續將使用TaskScheduler.Current(在這種情況下是TaskScheduler.Default,線程池任務調度程序)。

當您通過Task.Run啓動另一個任務時,您正在線程池上顯式執行它。所以當它到達其方法的末尾時,它完成其返回的任務,導致延續執行(同步)。由於await捕獲的任務調度程序是線程池調度程序(因此與延續兼容),因此它將直接執行DoSomething的下一部分。

請注意,這裏有一個競爭條件。 DoSomething的下一部分將僅在已連接時纔會同步執行,作爲Task.Run返回的任務的延續。在我的機器上,第一個Task.Run將在另一個線程上重新開始DoSomething,因爲在Task.Run委託完成時不會附加延續;第二個Task.Run確實在同一個線程上恢復DoSomething

所以我修改了代碼,使其更具確定性;驗證碼:

static Task DoSomething() 
{ 
    return Task.Run(async() => 
    { 
     Console.WriteLine("DoSomething-1 sleeping {0}", Thread.CurrentThread.ManagedThreadId); 
     await Task.Run(() => 
     { 
      Console.WriteLine("DoSomething-1 sleep {0}", Thread.CurrentThread.ManagedThreadId); 
      Thread.Sleep(100); 
     }); 
     Console.WriteLine("DoSomething-1 awake {0}", Thread.CurrentThread.ManagedThreadId); 

     Console.WriteLine("DoSomething-2 sleeping {0}", Thread.CurrentThread.ManagedThreadId); 
     var task = Task.Run(() => 
     { 
      Console.WriteLine("DoSomething-2 sleep {0}", Thread.CurrentThread.ManagedThreadId); 
     }); 
     Thread.Sleep(100); 
     await task; 
     Console.WriteLine("DoSomething-2 awake {0}", Thread.CurrentThread.ManagedThreadId); 
    }); 
} 

(我的機器上),同時顯示從競爭條件的可能性:

Start 8 
DoSomething-1 sleeping 9 
DoSomething-1 sleep 10 
DoSomething-1 awake 10 
DoSomething-2 sleeping 10 
DoSomething-2 sleep 11 
DoSomething-2 awake 10 
End 8 

順便說一句,你的Task.Yield使用不正確;你必須await的結果才能真正做任何事情。

請注意,此行爲(await使用ExecuteSynchronously)是未記錄的實現細節,未來可能會更改。

+0

我有一個與您的代碼示例稍有關聯的問題。 Task.Run是否使用System.Threading.ThreadPool或Windows.System.Threading.ThreadPool?如果是後者,並且您的處理程序使用RunAsync方法進行排隊,則根據http://msdn.microsoft.com/en-us/library/windowsphone/develop/jj248672.aspx,處理程序不應使用async關鍵字。那是對的嗎。或者,這一切是如何工作的? –

+0

我不確定WinRT實現的細節。但是,我可以肯定地說,將'async' lambda傳遞給'Task.Run'是安全的。 –

1

當您沒有指定要使用哪個調度程序時,您決定決定在哪裏/如何運行您的任務。 await確實要做的就是將所有代碼後面的等待完成的任務放入繼續任務中,該任務在等待完成的任務完成後運行。在很多情況下,調度程序會說:「嘿,我剛剛完成了線程X上的一項任務,並且還有一個延續任務......因爲線程X完成了,我將重新使用它來繼續!這正是你所看到的行爲。 (有關更多詳細信息,請參閱http://msdn.microsoft.com/en-US/library/vstudio/hh156528.aspx)。

如果您手動創建延續(而不是讓await爲您執行),則可以更準確地瞭解延續運行的方式和位置。 (有關延續選項,請參閱http://msdn.microsoft.com/en-us/library/system.threading.tasks.taskcontinuationoptions.aspx,您可以將其傳遞至Task.ContinueWith()。)