VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > c#编程 >
  • 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:捕获超时或主动取消,区分正常取消和异常。
基础知识拓展

  1. HttpClient最佳实践对比
    | 方式 | 优点 | 缺点 | 适用场景 |
    | ---- | ---- | ---- | ---- |
    | 静态复用 | 简单高效,无额外依赖 | 无法动态修改配置(如超时) | 小型项目、固定配置场景 |
    | IHttpClientFactory | 支持重试/超时/配置隔离,自动管理 | 需要依赖注入,配置稍复杂 | 中大型项目、微服务场景 |

  2. 异步Web请求核心原理
    异步HTTP请求底层依赖Socket异步IO:
    1.调用GetAsync时,.NET向操作系统发起异步Socket请求;
    2.操作系统处理请求时,释放当前线程回线程池;
    3.API响应到达后,操作系统通知.NET,线程池分配线程继续处理响应。
    优势:线程不被阻塞,能处理更多请求(吞吐量提升5-10倍)。

  3. 并发调用限制
    第三方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. 常见误区
    误区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


相关教程