C#异步HTTP请求两种调用方式的误区解析与选型建议
C#异步HTTP请求的认知误区解析与方案选择
共用异步方法代码
public static async Task<string> FetchDataFromUrl(string url) { await Console.Out.WriteLineAsync(Thread.CurrentThread.ManagedThreadId.ToString()); try { using (HttpClient client = new HttpClient()) { return await client.GetStringAsync(new Uri(url)); } } catch (Exception ex) { await Console.Out.WriteLineAsync(ex.Message); throw; } }
两种调用方式的认知误区解析
1. Task.WhenAll调用的误区
调用代码:
string url1 = "https://jsonplaceholder.typicode.com/posts/1"; string url2 = "https://jsonplaceholder.typicode.com/comments/1"; var results = await Task.WhenAll(FetchDataFromUrl(url1), FetchDataFromUrl(url2));
你认为Task.WhenAll会让任务在不同线程并行执行,但输出显示线程ID都是1,核心误区在于混淆了异步并行和多线程并行:
- 异步方法的执行分为同步启动和异步等待两个阶段:调用
FetchDataFromUrl时,会同步执行到第一个await(即Console.Out.WriteLineAsync),这部分代码是在调用线程(主线程)上执行的,所以你看到的线程ID都是主线程ID; HttpClient.GetStringAsync是IO异步操作,这类操作不需要占用线程等待IO完成,当任务进入异步等待阶段时,线程会被释放回线程池。两个请求的IO操作是同时进行的(真正的并行),只是同步启动阶段都在主线程执行,所以开头的线程ID输出一致。
2. 连续await调用的误区
调用代码:
string url1 = "https://jsonplaceholder.typicode.com/posts/1"; string url2 = "https://jsonplaceholder.typicode.com/comments/1"; List<string> results = new List<string> { await FetchDataFromUrl(url1), await FetchDataFromUrl(url2), };
你认为连续await会让任务顺序并发执行,这是对await非阻塞特性的误解:
await的非阻塞是指等待时不会占用当前线程,但它会暂停当前方法的执行流程,直到被等待的任务完成;- 这里第一个
await FetchDataFromUrl(url1)会等待第一个请求完全完成后,才会执行第二个await FetchDataFromUrl(url2),两个请求是串行执行的,而非并发。
方案选择
显然Task.WhenAll的方案更适合多URL请求的场景:
- 它能同时启动多个异步IO任务,让多个HTTP请求的IO操作并行进行,大幅缩短总耗时;
- 连续
await的串行方式会等待前一个请求完成后才启动下一个,总耗时约为两个请求耗时之和,效率远低于并行方案。
内容的提问来源于stack exchange,提问作者Jeffrey Willis
相关产品推荐
相关产品推荐

