![](/img/trans.png)
[英]When I using a thread to run HttpClient.GetAsync(...) and it never returns response
[英]HttpClient.GetAsync(…) never returns when using await/async
編輯: 這個問題看起來可能是同樣的問題,但沒有回應......
編輯:在測試用例 5 中,任務似乎停留在WaitingForActivation
狀態。
我在 .NET 4.5 中使用 System.Net.Http.HttpClient 遇到了一些奇怪的行為 - 其中“等待”調用(例如) httpClient.GetAsync(...)
將永遠不會返回。
這僅在使用新的 async/await 語言功能和 Tasks API 時發生在某些情況下 - 代碼似乎總是在僅使用延續時工作。
這是一些重現問題的代碼 - 將其放入 Visual Studio 11 中的新“MVC 4 WebApi 項目”中以公開以下 GET 端點:
/api/test1
/api/test2
/api/test3
/api/test4
/api/test5 <--- never completes
/api/test6
這里的每個端點都返回相同的數據(來自 stackoverflow.com 的響應標頭),除了永遠不會完成的/api/test5
。
重現代碼:
public class BaseApiController : ApiController
{
/// <summary>
/// Retrieves data using continuations
/// </summary>
protected Task<string> Continuations_GetSomeDataAsync()
{
var httpClient = new HttpClient();
var t = httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead);
return t.ContinueWith(t1 => t1.Result.Content.Headers.ToString());
}
/// <summary>
/// Retrieves data using async/await
/// </summary>
protected async Task<string> AsyncAwait_GetSomeDataAsync()
{
var httpClient = new HttpClient();
var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead);
return result.Content.Headers.ToString();
}
}
public class Test1Controller : BaseApiController
{
/// <summary>
/// Handles task using Async/Await
/// </summary>
public async Task<string> Get()
{
var data = await Continuations_GetSomeDataAsync();
return data;
}
}
public class Test2Controller : BaseApiController
{
/// <summary>
/// Handles task by blocking the thread until the task completes
/// </summary>
public string Get()
{
var task = Continuations_GetSomeDataAsync();
var data = task.GetAwaiter().GetResult();
return data;
}
}
public class Test3Controller : BaseApiController
{
/// <summary>
/// Passes the task back to the controller host
/// </summary>
public Task<string> Get()
{
return Continuations_GetSomeDataAsync();
}
}
public class Test4Controller : BaseApiController
{
/// <summary>
/// Handles task using Async/Await
/// </summary>
public async Task<string> Get()
{
var data = await AsyncAwait_GetSomeDataAsync();
return data;
}
}
public class Test5Controller : BaseApiController
{
/// <summary>
/// Handles task by blocking the thread until the task completes
/// </summary>
public string Get()
{
var task = AsyncAwait_GetSomeDataAsync();
var data = task.GetAwaiter().GetResult();
return data;
}
}
public class Test6Controller : BaseApiController
{
/// <summary>
/// Passes the task back to the controller host
/// </summary>
public Task<string> Get()
{
return AsyncAwait_GetSomeDataAsync();
}
}
您正在濫用 API。
情況是這樣的:在 ASP.NET 中,一次只有一個線程可以處理一個請求。 如有必要,您可以進行一些並行處理(從線程池中借用附加線程),但只有一個線程具有請求上下文(附加線程沒有請求上下文)。
這是由 ASP.NET SynchronizationContext
管理的。
默認情況下,當您await
Task
,該方法會在捕獲的SynchronizationContext
(或捕獲的TaskScheduler
,如果沒有SynchronizationContext
)上恢復。 通常,這正是您想要的:異步控制器操作將await
某些東西,當它恢復時,它會以請求上下文恢復。
所以,這就是test5
失敗的原因:
Test5Controller.Get
執行AsyncAwait_GetSomeDataAsync
(在 ASP.NET 請求上下文中)。AsyncAwait_GetSomeDataAsync
執行HttpClient.GetAsync
(在 ASP.NET 請求上下文中)。HttpClient.GetAsync
返回一個未完成的Task
。AsyncAwait_GetSomeDataAsync
等待Task
; 由於未完成, AsyncAwait_GetSomeDataAsync
返回未完成的Task
。Test5Controller.Get
阻塞當前線程,直到該Task
完成。HttpClient.GetAsync
返回的Task
就完成了。AsyncAwait_GetSomeDataAsync
嘗試在 ASP.NET 請求上下文中恢復。 但是,該上下文中已經有一個線程:在Test5Controller.Get
阻塞的線程。這就是其他人工作的原因:
test1
、 test2
和test3
): Continuations_GetSomeDataAsync
將延續安排到 ASP.NET 請求上下文之外的線程池。 這允許Continuations_GetSomeDataAsync
返回的Task
完成,而無需重新輸入請求上下文。test4
和test6
):由於Task
被等待,ASP.NET 請求線程不會被阻塞。 這允許AsyncAwait_GetSomeDataAsync
在准備好繼續時使用 ASP.NET 請求上下文。以下是最佳實踐:
async
方法中,盡可能使用ConfigureAwait(false)
。 在您的情況下,這會將AsyncAwait_GetSomeDataAsync
更改為var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
Task
; 它一直是async
的。 換句話說,使用await
而不是GetResult
( Task.Result
和Task.Wait
也應該替換為await
)。 這樣,您將獲得兩個好處:延續( AsyncAwait_GetSomeDataAsync
方法的其余部分)在基本線程池線程上運行,該線程池線程不必進入 ASP.NET 請求上下文; 並且控制器本身是async
(它不會阻塞請求線程)。
更多信息:
async
/ await
介紹帖子,其中包括Task
等待者如何使用SynchronizationContext
的簡要說明。SynchronizationContext
將請求上下文限制為一次只有一個線程。2012 年 7 月 13 日更新:將此答案合並到博客文章中。
編輯:通常盡量避免執行以下操作,除非作為避免死鎖的最后努力。 閱讀 Stephen Cleary 的第一條評論。
從這里快速修復。 而不是寫:
Task tsk = AsyncOperation();
tsk.Wait();
嘗試:
Task.Run(() => AsyncOperation()).Wait();
或者,如果您需要結果:
var result = Task.Run(() => AsyncOperation()).Result;
從源(編輯以匹配上面的例子):
AsyncOperation 現在將在沒有 SynchronizationContext 的 ThreadPool 上調用,並且 AsyncOperation 內部使用的延續不會被強制返回到調用線程。
對我來說,這看起來像是一個可用的選項,因為我沒有辦法讓它一直異步(我更喜歡)。
從來源:
確保 FooAsync 方法中的 await 找不到要封送回的上下文。 最簡單的方法是從 ThreadPool 調用異步工作,例如通過將調用包裝在 Task.Run 中,例如
int Sync() { return Task.Run(() => Library.FooAsync()).Result; }
現在將在 ThreadPool 上調用 FooAsync,其中不會有 SynchronizationContext,並且在 FooAsync 內部使用的延續不會被強制返回到調用 Sync() 的線程。
由於您正在使用.Result
或.Wait
或await
這最終會導致您的代碼死鎖。
您可以在async
方法中使用ConfigureAwait(false)
來防止死鎖
像這樣:
var result = await httpClient.GetAsync("http://stackoverflow.com", HttpCompletionOption.ResponseHeadersRead)
.ConfigureAwait(false);
您可以盡可能將
ConfigureAwait(false)
用於 Don't Block Async Code 。
這兩個學校並不是真的排斥。
這是您只需要使用的場景
Task.Run(() => AsyncOperation()).Wait();
或類似的東西
AsyncContext.Run(AsyncOperation);
我有一個位於數據庫事務屬性下的 MVC 操作。 這個想法(可能)是在出現問題時回滾操作中所做的一切。 這不允許上下文切換,否則事務回滾或提交本身就會失敗。
我需要的庫是異步的,因為它應該運行異步。
唯一的選擇。 將其作為普通同步調用運行。
我只是對每個人說自己的。
我將把它放在這里更多的是為了完整性,而不是與 OP 直接相關。 我花了將近一天的時間調試一個HttpClient
請求,想知道為什么我從來沒有得到響應。
最后發現我忘記在調用堆棧中await
async
調用了。
感覺就像缺少一個分號一樣好。
在我的情況下,'await' 在執行請求時由於異常而從未完成,例如服務器沒有響應等。用 try..catch 包圍它以識別發生了什么,它也會優雅地完成你的 'await'。
public async Task<Stuff> GetStuff(string id)
{
string path = $"/api/v2/stuff/{id}";
try
{
HttpResponseMessage response = await client.GetAsync(path);
if (response.StatusCode == HttpStatusCode.OK)
{
string json = await response.Content.ReadAsStringAsync();
return JsonUtility.FromJson<Stuff>(json);
}
else
{
Debug.LogError($"Could not retrieve stuff {id}");
}
}
catch (Exception exception)
{
Debug.LogError($"Exception when retrieving stuff {exception}");
}
return null;
}
我在這看:
http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.taskawaiter(v=vs.110).aspx
和這里:
並看到:
此類型及其成員旨在供編譯器使用。
考慮到await
版本是有效的,並且是“正確”的做事方式,你真的需要回答這個問題嗎?
我的投票是: 濫用API 。
我正在使用許多等待,所以我沒有得到響應,我轉換為同步調用它開始工作
using (var client = new HttpClient())
using (var request = new HttpRequestMessage())
{
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Method = HttpMethod.Get;
request.RequestUri = new Uri(URL);
var response = client.GetAsync(URL).Result;
response.EnsureSuccessStatusCode();
string responseBody = response.Content.ReadAsStringAsync().Result;
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.