2015-11-21 73 views
1

我想弄清楚我正在使用WebRequest/WebResponse轉換爲HttpClient的TPL程序中發生了死鎖。爲什麼Task.Run(()=> something)調用了一個等待死鎖的同步方法?

我有,創建100個任務,並把它們放入數組循環:

var putTasks = new Task[100]; 
for (var i = 0; i < 100; i++) 
{ 
    putTasks[i] = Task.Run(() => client.Put("something")); 
} 
Task.WaitAll(putTasks); 

的代碼是掛在Task.WaitAll來看,在服務器端顯示的第一個請求完成,但其餘的都沒有。

關於這個問題的奇怪的部分(是的,它應該是異步通過,但它現在不正確,我試圖理解爲什麼它是完全異步之前它是死鎖)是客戶端。是看起來像這樣的同步方法:

string Put(string thing) { return PutAsync(string thing).GetAwaiter().GetResult(); }

和PutAsync樣子:

async Task<string> PutAsync(string thing) 
{ 
    //httpClient built up here 
    HttpContent content = new StringContent(thing); 

    var response = await httpClient.PutAsync("http://myuri", content); 
    var result = await response.Content.ReadAsStreamAsync(); 
    // Do some stuff 
} 

它掛在裏面PutAsync是response = await httpClient.PutAsync部分。我在UI線程中看到了大量關於死鎖的答案,但沒有一個修復(例如更改捕獲的上下文)有幫助。

理想情況下,我只是將所有異步完成,等待一切,但正如我所說的,我試圖弄清楚爲什麼僵局會發生,以及在發生什麼事情之前,我將所有事情分開 - 另一個問題這是一個其他人使用的庫,而且接口是同步方法,所以我不能只是將其更改爲異步並更新一堆隱藏的代碼。

+0

PutAsync將創建它自己的任務。所以你聲明的任務數組是不需要的。刪除'Task.Run(()'把你的代碼放在一個方法中並標記爲'async'並等待'client.Put'調用 – William

+0

我正在「掩飾」PutAsync'創建的任務 - 基本上,我是將一個同步庫轉換爲在後臺代碼中使用異步,所以'Put'方法不會返回一個'Task',它只是返回一個'string'。實際上,我可能在這裏完全錯誤 - 如果我讓它坐得夠久,它是否工作,所以我不知道它是否阻止共享HttpClient的連接池或其他東西 – HighlyUnavailable

+0

@HighlyUnavailable如果它確實工作,那麼它不是一個死鎖。它可能是限制因爲有多少請求可以發送到 – i3arnon

回答

2

問題是所有正在運行的任務調用填充線程池的同步API,以便HttpClient必須執行的異步調用沒有線程運行。

3

然後請發帖回答啓發我,我應該如何利用它提供的同步方法的API(如client.Put(字符串件事)方法,並把它掛到幕後異步方法。

You don't. Doing so doesn't provide any benefit.只有誰提供的庫的人應該換他們知道事實call some type of Completion Port電話。創建一個單獨的線程(Task.Run())等待資源簡直是浪費資源(也有例外)。

我不能只是將其更改爲異步並更新一堆隱藏的代碼。

而你不應該。

它掛在裏面PutAsync的部分是死鎖的response = await httpClient.PutAsync

99%造成的,因爲在呼叫者MethodAsync()被錯誤地進行的,沒有特定的代碼,知道爲什麼是無法回答的。

如果你知道你正在使用完成端口

我不能證明一個PutAsync因爲要求建立一個真正的異步方法並非適用於HttpClient這是唯一有用的。相反,我將創建對SqlCommandExecuteNonQuery()

public static Task<int> ExecuteNonQueryAsync(this SqlCommand sqlCommand) 
{ 
    var tcs = new TaskCompletionSource<int>(); 

    sqlCommand.BeginExecutedNonQuery(result => 
    { 
    tcs.SetResult(sqlCommand.EndExecutedNonQuery()); 
    }); 

    return await tcs.Task; 
} 

授予此編碼非常糟糕,沒有的try/catch等,但它不使用Task.Run()啓動另一個線程,什麼也不做。

現在,如果你想要做正確的並行任務與異步任務調用,然後這樣的事情是比較合適的(而且不浪費線程):

public async IEnumerable<HttpResponseMessage> GoCrazy() 
{ 
    var putTasks = new List<Task<HttpResponseMessage>>(); 

    for (var i = 0; i < 100; i++) 
    { 
    var task = client.PutAsync("something"); 
    putTasks.Add(task); 
    } 

    // WhenAll not WaitAll 
    var result = await Task.WhenAll(putTasks); 

    return result; 
} 
+0

我強烈推薦觀看[C#和Visual Basic的異步最佳實踐](https://www.youtube.com/watch?v=f8v5EQRfQaA)並閱讀[Stephen Cleary的博客](http ://blog.stephencleary.com/)這裏有一大堆好的信息 –

+0

我這樣做的唯一原因是我希望這個庫在.NET的新版本下編譯,並且從DNXCore50開始,Microsoft已經完全刪除了同步HTTP客戶端(WebRequest/WebResponse)並將其替換爲智能h要求使用HttpClient。您的SQL客戶端示例沒有幫助,因爲如您所說,「創建真正的異步方法的要求在HttpClient上不可用」。無論如何,我已經知道了這個問題,並且試圖對多線程進行多線程處理的任務阻塞了http調用任務。 – HighlyUnavailable

相關問題