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