[英]C# async/await with HttpClient GetAsync - non-async calling method
這是從無法標記為異步的主 function 調用時的異步/等待 - function必須同步運行,但由於 HttpClient 是異步的,因此在某些時候,等待必須使事情正常停止。 我已經閱讀了很多關於.Result
或.Wait
如何導致死鎖的內容,但這些是唯一真正使代碼同步的版本。
這是一個示例程序,大致遵循代碼的作用——注意循環中的 4 次嘗試——只有其中一次嘗試以正確的順序輸出數據。 這個結構有什么根本性的錯誤嗎/我不能使用示例 #3 嗎?
我能找到的最接近的例子是在這里,那是調用 function 可以異步的地方,而這個不能。 我試過將private static void Process()
異步並使用Task.Run(async ()=> await Process());
調用它。 但它仍然出現故障。 唯一始終如一的工作是Wait/Result
,它可能會死鎖,特別是我讀過的 HttpClient 。 有什么想法嗎?
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
namespace TestAsync
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("1. Calling process at {0:mm.ss.fff}", DateTime.Now);
// Call 1
Process();
Console.WriteLine("1. Calling process at {0:mm.ss.fff}", DateTime.Now);
// Call 2
Process();
Console.ReadKey();
}
private static void Process()
{
Console.WriteLine("2. Calling CallAsyncTest at {0:mm.ss.fff}", DateTime.Now);
for (int i = 1; i < 4; i++)
{
// Try 1 - doesn't work
//CallAsyncTest(i);
// Try 2 - doesn't work
//Task.Run(async () => await CallAsyncTest(i));
// Try 3 - but works not recommended
CallAsyncTest(i).Wait();
// Try 4 - doesn't work
//CallAsyncTest(i).ConfigureAwait(false); ;
}
}
private static async Task CallAsyncTest(int i)
{
Console.WriteLine("{0}. Calling await AsyncTest.Start at {1:mm.ss.fff}", i + 2, DateTime.Now);
var x = await AsyncTest.Start(i);
}
}
public class AsyncTest
{
public static async Task<string> Start(int i)
{
Console.WriteLine("{0}. Calling await Post<string> at {1:mm.ss.fff}", i + 3, DateTime.Now);
return await Post<string>(i);
}
private static async Task<T> Post<T>(int i)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
using (HttpClient httpClient = new HttpClient(new HttpClientHandler()))
{
using (HttpResponseMessage response = await httpClient.GetAsync("https://www.google.com"))
{
using (HttpContent content = response.Content)
{
string responseString = await content.ReadAsStringAsync();
Console.WriteLine("response");
}
}
}
return default(T);
}
}
但這些是唯一真正使代碼同步的版本
確切地說,您不應該僅出於這個原因使用它。 您不想在執行 I/O 時鎖定您的主線程,否則您的應用程序在最字面意義上就是垃圾。
如果您出於某種原因不想讓整個應用程序具有async
感知能力,您仍然可以使用適當的async
模式。 請記住, await
只不過是Task
class 已經為您提供的語法糖:一種在任務完成后繼續執行的方法。
因此,與其讓進程 function 鎖定主線程, Task.WhenAll
.ContinueWith()
如果它們是應該並行運行)並立即返回。 然后,continuation 負責使用接收到的數據(如果成功)或錯誤信息(如果失敗)更新您的 UI。
function 必須同步運行
正如其他人指出的那樣,最好的解決方案是 go async all the way 。 但我假設有充分的理由說明這是不可能的。
在某些時候, await 必須讓事情優雅地停止。
在某些時候,您的代碼將不得不阻塞異步代碼,是的。
我已經閱讀了很多有關 .Result 或 .Wait 如何導致死鎖的內容,但這些是唯一真正使代碼同步的版本。
正確的。 阻塞異步代碼是使其同步的唯一方法。
它實際上與Result
或Wait
本身無關——它是任何一種阻塞。 並且在某些情況下阻塞異步代碼會導致死鎖。 具體來說,當所有這些條件都為真時:
await
默認情況下確實捕獲上下文。控制台應用程序和 ASP.NET 核心應用程序沒有上下文,因此在這些場景中不會發生死鎖。
唯一始終如一的工作是 Wait/Result ,它可以死鎖......有什么想法嗎?
您可以選擇一些技巧來阻止異步代碼。
其中之一就是直接阻止。 這適用於 Console / ASP.NET Core 應用程序(因為它們沒有會導致死鎖的上下文)。 我建議在這種情況下使用GetAwaiter().GetResult()
以避免在使用Result
/ Wait()
時出現異常包裝器:
CallAsyncTest(i).GetAwaiter().GetResult();
但是,如果應用程序是 UI 應用程序或舊版 ASP.NET 應用程序,則該方法將不起作用。 在這種情況下,您可以將異步工作推送到線程池線程並阻塞該線程。 線程池線程在上下文之外運行,因此它避免了死鎖,但是這個 hack 意味着CallAsyncTest
必須能夠在線程池線程上運行 - 不能訪問 UI 元素或HttpContext.Current
或任何其他依賴於上下文的東西,因為上下文不會在那里:
Task.Run(() => CallAsyncTest(i)).GetAwaiter().GetResult();
沒有適用於所有情況的解決方案。 如果沒有上下文(例如,控制台應用程序),則直接阻止會起作用。 如果代碼可以在任意線程池線程上運行,則在Task.Run
上阻塞會起作用。 還有一些其他的 hack 可用,但這兩個是最常見的。
這就是其他嘗試失敗的原因。
這個開始任務但根本不等待:
// Try 1 - doesn't work
CallAsyncTest(i);
這一個在線程池線程中啟動任務但根本不等待:
// Try 2 - doesn't work
Task.Run(async () => await CallAsyncTest(i));
“Try 4”與“Try 1”完全相同。 對ConfigureAwait
的調用不執行任何操作,因為沒有await
配置。 所以它也只是啟動任務但根本不等待:
// Try 4 - doesn't work
CallAsyncTest(i).ConfigureAwait(false);
為什么不使 Process 方法異步,然后使用正常的異步等待模式?
Process 簽名將是
private static async Task Process()
這將允許您使用await CallAsyncTest(i)
你會在 main 方法上等待它,就像這樣:
Process.GetAwaiter().GetResult();
這相當於 C# 7.1 中引入的 async main 的實現
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.