-
C#异步Web请求——HTTP客户端异步调用
第一部分:C#基础入门
异步Web请求——HTTP客户端异步调用
实例介绍
你有没有遇到过这样的场景?
做电商APP时,用户下单后同步调用支付、物流、通知API,导致下单流程耗时5秒以上,用户吐槽“反应慢”;
WPF程序里点击“获取天气”按钮,界面突然卡住1秒——原来是同步调用天气API阻塞了UI线程;
后端服务调用第三方接口时,每次新建HttpClient,运行一周后报“端口耗尽”错误,服务崩溃。
这些问题的核心是同步Web请求阻vb.net教程C#教程python教程SQL教程access 2010教程
塞线程,而异步Web请求通过非阻塞IO释放线程,让程序在等待API响应时继续处理其他任务。本节通过4个典型场景,带你掌握异步HTTP调用的正确姿势,避开90%的常见坑点。
需求分析
本节需解决3类核心场景,同时规避错误写法:
1.异步GET请求:获取第三方API数据(如天气、用户信息);
2.异步POST请求:提交数据到API(如用户注册、订单创建);
3.并发调用多个API:同时请求多个接口提升效率(如批量获取城市天气);
4.超时与取消:API调用超时自动终止,或用户主动取消请求;
5.错误写法危害:每次新建HttpClient导致端口耗尽、用Result/Wait引发死锁、同步调用阻塞线程。
目标:让你的API调用在高并发场景下稳定高效,UI程序响应流畅。
代码实现
前置条件:.NET 6+;需引用System.Net.Http(默认已包含)、System.Text.Json(序列化)。
准备工作:复用HttpClient(关键!)
错误写法:每次新建HttpClient(端口耗尽)
csharp
// ❌错误:每次调用新建HttpClient,导致TCP端口耗尽
public async Task GetDataWrong()
{
using var client = new HttpClient(); // 每次创建占用新端口
var response = await client.GetAsync("https://api.example.com/data");
}
正确写法:复用静态HttpClient或用IHttpClientFactory
csharp
// ✅推荐:静态复用HttpClient(适合简单场景)
private static readonly HttpClient _sharedHttpClient = new HttpClient()
{
Timeout = TimeSpan.FromSeconds(5) // 全局超时设置
};
// ✅进阶:用IHttpClientFactory管理(.NET Core+推荐,支持重试/配置)
// Program.cs配置:builder.Services.AddHttpClient();
private readonly IHttpClientFactory _clientFactory;
public WeatherService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
场景1:异步GET请求(获取天气信息)
错误写法:同步阻塞或死锁
csharp
// ❌错误1:同步调用阻塞线程
public string GetWeatherWrong1(string city)
{
var result = _sharedHttpClient.GetStringAsync($"https://api.weatherapi.com/v1/current.json?key=xxx&q={city}").Result;
return result; // 死锁风险:捕获上下文导致线程等待
}
// ❌错误2:异步方法中未处理HTTP错误
public async Task<string> GetWeatherWrong2(string city)
{
var response = await _sharedHttpClient.GetAsync($"https://api.weatherapi.com/v1/current.json?key=xxx&q={city}");
return await response.Content.ReadAsStringAsync(); // 忽略404/500错误
}
正确写法:异步调用+错误处理
csharp
/// <summary>
/// 异步获取城市天气(带错误处理)
/// </summary>
public async Task<WeatherResponse?> GetWeatherAsync(string city)
{
try
{
var url = $"https://api.weatherapi.com/v1/current.json?key=YOUR_API_KEY&q={city}";
// ✅异步GET请求,ConfigureAwait(false)避免非UI场景死锁
using var response = await _sharedHttpClient.GetAsync(url).ConfigureAwait(false);
// ✅检查HTTP状态码(2xx成功,否则抛出异常)
response.EnsureSuccessStatusCode();
// ✅异步读取响应并序列化
var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
return JsonSerializer.Deserialize<WeatherResponse>(json);
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
{
Console.WriteLine($"城市{city}未找到");
return null;
}
catch (OperationCanceledException)
{
Console.WriteLine("请求超时或被取消");
return null;
}
}
// 天气响应实体类
public class WeatherResponse
{
public Location Location { get; set; } = null!;
public Current Current { get; set; } = null!;
}
public class Location { public string Name { get; set; } = null!; }
public class Current { public double TempC { get; set; } }
场景2:异步POST请求(提交用户反馈)
正确写法:异步提交JSON数据
csharp
/// <summary>
/// 异步提交用户反馈
/// </summary>
public async Task<bool> SubmitFeedbackAsync(Feedback feedback)
{
try
{
var jsonContent = new StringContent(
JsonSerializer.Serialize(feedback),
Encoding.UTF8,
"application/json" // 指定Content-Type
);
// ✅异步POST请求
using var response = await _sharedHttpClient.PostAsync(
"https://api.example.com/feedback",
jsonContent
).ConfigureAwait(false);
return response.IsSuccessStatusCode;
}
catch (HttpRequestException)
{
return false;
}
}
// 反馈实体类
public class Feedback { public string UserId { get; set; } = null!; public string Content { get; set; } = null!; }
场景3:并发调用多个API(批量获取天气)
正确写法:用Task.WhenAll并发执行
csharp
/// <summary>
/// 并发获取多个城市天气
/// </summary>
public async Task<List<WeatherResponse?>> GetMultiCityWeatherAsync(List<string> cities)
{
// ✅创建多个异步任务(不立即执行)
var tasks = cities.Select(city => GetWeatherAsync(city));
// ✅等待所有任务完成(并发执行,总耗时≈最长单个请求时间)
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
return results.ToList();
}
// 调用示例:同时获取北京、上海、广州天气
var cities = new List<string> { "Beijing", "Shanghai", "Guangzhou" };
var weathers = await GetMultiCityWeatherAsync(cities);
场景4:超时与主动取消请求
正确写法:用CancellationTokenSource控制
csharp
/// <summary>
/// 带超时和取消的异步请求
/// </summary>
public async Task<WeatherResponse?> GetWeatherWithCancelAsync(string city, CancellationToken userCancelToken)
{
using var cts = CancellationTokenSource.CreateLinkedTokenSource(userCancelToken);
cts.CancelAfter(TimeSpan.FromSeconds(3)); // 3秒超时
try
{
var url = $"https://api.weatherapi.com/v1/current.json?key=YOUR_API_KEY&q={city}";
using var response = await _sharedHttpClient.GetAsync(url, cts.Token).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync(cts.Token).ConfigureAwait(false);
return JsonSerializer.Deserialize<WeatherResponse>(json);
}
catch (OperationCanceledException)
{
Console.WriteLine("请求超时或用户取消");
return null;
}
}
// UI调用示例(WPF):用户点击取消按钮终止请求
private CancellationTokenSource? _cts;
private async void BtnGetWeather_Click(object sender, RoutedEventArgs e)
{
_cts = new CancellationTokenSource();
var weather = await GetWeatherWithCancelAsync("Beijing", _cts.Token);
// 更新UI...
}
private void BtnCancel_Click(object sender, RoutedEventArgs e)
{
_cts?.Cancel(); // 主动取消请求
}
逐行讲解
关键1:HttpClient复用
静态复用:static readonly HttpClient避免每次创建占用端口,适合简单场景;
IHttpClientFactory:.NET Core+推荐,自动管理连接池、支持重试策略(如Polly)、配置隔离。
关键2:异步方法使用
GetAsync/PostAsync:异步HTTP请求方法,返回Task
ConfigureAwait(false):非UI场景不需要捕获上下文(如Web API),避免死锁+提升性能;
EnsureSuccessStatusCode():HTTP状态码非2xx时抛出HttpRequestException,强制处理错误。
关键3:并发与取消
Task.WhenAll:并发执行多个任务,总耗时≈最长单个任务时间,比同步串行快N倍;
CancellationTokenSource:
CancelAfter:设置超时自动取消;
CreateLinkedTokenSource:结合用户主动取消(如UI按钮)和超时,灵活控制请求生命周期。
关键4:错误处理
HttpRequestException:捕获网络错误(如无网络)或HTTP错误(如404/500);
OperationCanceledException:捕获超时或主动取消,区分正常取消和异常。
基础知识拓展
-
HttpClient最佳实践对比
| 方式 | 优点 | 缺点 | 适用场景 |
| ---- | ---- | ---- | ---- |
| 静态复用 | 简单高效,无额外依赖 | 无法动态修改配置(如超时) | 小型项目、固定配置场景 |
| IHttpClientFactory | 支持重试/超时/配置隔离,自动管理 | 需要依赖注入,配置稍复杂 | 中大型项目、微服务场景 | -
异步Web请求核心原理
异步HTTP请求底层依赖Socket异步IO:
1.调用GetAsync时,.NET向操作系统发起异步Socket请求;
2.操作系统处理请求时,释放当前线程回线程池;
3.API响应到达后,操作系统通知.NET,线程池分配线程继续处理响应。
优势:线程不被阻塞,能处理更多请求(吞吐量提升5-10倍)。 -
并发调用限制
第三方API限流:避免同时调用过多API导致被封禁(如用SemaphoreSlim限制并发数);
示例:
csharp
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(5); // 限制5并发
public async Task<WeatherResponse?> GetWeatherWithLimitAsync(string city)
{
await _semaphore.WaitAsync(); // 等待许可
try { return await GetWeatherAsync(city); }
finally { _semaphore.Release(); } // 释放许可
}
-
常见误区
误区1:异步请求比同步快?
No!异步请求的优势是线程利用率,而非绝对速度。单请求场景下,同步和异步速度几乎相同,但高并发下异步能处理更多请求。
误区2:HttpClient是线程安全的?
Yes!HttpClient的GetAsync/PostAsync等方法是线程安全的,可复用。
误区3:using HttpClient是正确的?
No!每次using会销毁连接池,导致端口耗尽。应复用或用IHttpClientFactory。
总结
异步Web请求是高并发程序和流畅UI的必备技能,核心要点:
1.复用HttpClient:避免每次创建导致端口耗尽;
2.用异步方法:GetAsync/PostAsync+await释放线程;
3.处理错误与超时:捕获HttpRequestException和OperationCanceledException;
4.并发控制:Task.WhenAll提升效率,SemaphoreSlim限制并发;
5.非UI场景加ConfigureAwait(false):避免死锁+提升性能。
掌握这些技巧,你的程序能轻松应对高并发场景,用户再也不会吐槽“反应慢”!
本站原创,转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49453.html










