[英]Async-Await no deadlock where a deadlock is expected
眾所周知,異步方法的同步等待會導致死鎖(例如,請參閱“ 不要阻止異步代碼” )
我在事件處理程序中有以下代碼,用於在Windows窗體應用程序中單擊按鈕(即,在安裝了UI SynchronizationContext
情況下調用代碼)。
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, new Uri("http://www.google.com"));
Task<HttpResponseMessage> t = client.SendAsync(request);
t.Wait();
var response = t.Result;
我完全希望代碼在單擊按鈕時死鎖。 但是,我實際看到的是同步等待 - 對話框暫時沒有響應,然后像往常一樣接受事件。 當我嘗試同步等待客戶端異步方法時,我一直看到死鎖。 但是,同步等待SendAsync
或ReadAsByteArrayAsync
等庫異步方法似乎不會死鎖。 有人可以解釋這種行為嗎?
.NET庫中的異步方法的實現是否在內部使用await語句,因此必須將continuation編組回原始的SynchronizationContext?
注意:如果我定義了一個客戶端方法,請說
public async Task<byte[]> wrapperMethod()
{
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, new Uri("http://www.google.com"));
var response = await client.SendAsync(request);
return await response.Content.ReadAsByteArrayAsync();
}
然后說byte[] byteArray = wrapperMethod().Result;
在按鈕單擊處理程序中,我確實獲得了死鎖。
.NET庫中的異步方法的實現是否在內部使用await語句?
一般來說,沒有。 我還沒有在.NET框架中看到一個在內部使用async-await的實現。 它確實使用任務和延續,但不使用async
和await
關鍵字帶來的編譯器魔法。
使用async-await非常簡單,因為代碼看起來是同步的,但實際上是異步運行的。 但這種簡單性在性能上的代價非常小 。
對於大多數消費者來說,這個價格是值得付出的,但框架本身也盡可能地高效。
但是,同步等待
SendAsync
或ReadAsByteArrayAsync
等庫異步方法似乎不會死鎖。
死鎖是await默認行為的結果。 當您等待未完成的任務時,將捕獲SynchronizationContext
,當它完成時,將在該SynchronizationContext
上恢復繼續(如果存在)。 當沒有異步,等待,捕獲的SynchronizationContext
等時,這種死鎖不會發生。
HttpClient.SendAsync
專門使用TaskCompletionSource
返回任務,而不將方法標記為異步。 您可以在github上的實現中看到這一點 。
為async-await添加到現有類的大多數任務返回方法只是使用現有的異步API(即BeginXXX
/ EndXXX
)構建任務。 例如,這是TcpClient.ConnectAsync
:
public Task ConnectAsync(IPAddress address, int port)
{
return Task.Factory.FromAsync(BeginConnect, EndConnect, address, port, null);
}
當您使用async-await雖然在不需要捕獲SynchronizationContext
時通過使用ConfigureAwait(false)
來避免死鎖。 除非需要上下文(例如UI庫),否則建議庫應該總是使用它。
你不會通過阻止大多數開箱即用的Task
-returning .NET調用來導致死鎖,因為除非絕對必要,否則它們不會在內部觸及Task
啟動的SynchronizationContext
(兩個原因:性能和避免死鎖) 。
這意味着即使標准的.NET調用確實使用了async/await
(i3arnon說他們沒有 - 我不會爭辯,因為我根本就不知道),毫無疑問,他們會使用ConfigureAwait(false)
除非捕獲上下文是絕對必要的。
但這就是.NET框架。 至於你自己的代碼,如果你在客戶端調用wrapperMethod().Wait()
(或Result
),你會發現死鎖(前提是你運行的是非空的SynchronizationContext.Current
- 如果你使用的是Windows表格,這肯定是這樣的)。 為什么? 因為你沒有在不與UI交互的async
方法中使用ConfigureAwait(false)
來炫耀async/await
最佳實踐,導致狀態機生成不必要地在原始SynchronizationContext
上執行的延續。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.