簡體   English   中英

異步 - 等待沒有死鎖的死鎖

[英]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;

我完全希望代碼在單擊按鈕時死鎖。 但是,我實際看到的是同步等待 - 對話框暫時沒有響應,然后像往常一樣接受事件。 當我嘗試同步等待客戶端異步方法時,我一直看到死鎖。 但是,同步等待SendAsyncReadAsByteArrayAsync異步方法似乎不會死鎖。 有人可以解釋這種行為嗎?

.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的實現。 它確實使用任務和延續,但不使用asyncawait關鍵字帶來的編譯器魔法。

使用async-await非常簡單,因為代碼看起來是同步的,但實際上是異步運行的。 但這種簡單性在性能上的代價非常小

對於大多數消費者來說,這個價格是值得付出的,但框架本身也盡可能地高效。

但是,同步等待SendAsyncReadAsByteArrayAsync等庫異步方法似乎不會死鎖。

死鎖是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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM