VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > c#编程 >
  • C#网络编程之异步HTTP/WebSocket编程

第9章 异步HTTP/WebSocket编程
9.1 异步HTTP/WebSocket编程
一、为什么要搞异步?我踩过的坑
刚做C#网络开发时,我用同步代码写了一个批量HTTP请求工具,结果跑起来卡得要死——100个请求要等100秒,CPU还占满了。后来改成异步,100个请求只花了5秒,CPU使用率不到10%。这节我把自己从“同步坑”里爬出来的经验揉进去,用大白话讲透异步编程的核心,结合HTTP/WebSocket的实战代码,让你既能写高效的异步客户端,又能写高并发的异步服务器。
二、先搞懂异步的本质:不要让CPU等IO
网络编程的核心是IO密集型任务——大部分时间都在等网络响应(比如等服务器返回数据),而不是让CPU计算。同步代码会让线程在等IO时阻塞,而异步代码会让线程在等IO时去做其他事情,等IO完成后再回来处理结果。
类比:同步就像你去银行排队,排到你了才处理你的业务,前面的人没办完你就一直站着等;异步就像你取个号,然后去旁边玩手机,叫到你的号再去处理——这样银行的柜员(线程)可以同时服务更多人,效率更高。
三、C#异步编程基础:async/await是“语法糖”
C#的异步编程核心是async/await,它是Task的语法糖,让异步代码看起来像同步代码一样好写。

  1. 异步方法的规则
    方法返回值必须是Task(无返回值)、Task(有返回值)或ValueTask(高性能场景);
    方法名必须以Async结尾(约定俗成,比如GetAsync、SendAsync);
    方法内部必须有await关键字,否则会同步执行。
    示例:
    csharp
	// 异步方法:返回Task<int>
	public async Task<int> GetNumberAsync()
	{
	// 模拟IO操作:等待1秒
	await Task.Delay(1000);
	return 42;
	}
	
	// 调用异步方法
	public async Task Main()
	{
	int number = await GetNumberAsync();
	Console.WriteLine(number);
	}

四、异步HTTP编程:用HttpClient实现高并发请求
HttpClient的所有方法都是异步的(比如GetAsync、PostAsync),用async/await就能轻松实现高并发HTTP请求。

  1. 批量异步HTTP请求:比同步快10倍
    csharp
	using System;
	using System.Collections.Generic;
	using System.Net.Http;
	using System.Threading.Tasks;
	
	namespace AsyncHttpClientDemo;
	
	class Program
	{
	// 全局单例HttpClient(必须!否则会耗尽端口)
	private static readonly HttpClient _httpClient = new HttpClient();
	
	static async Task Main(string[] args)
	{
	// 100个要请求的URL
	var urls = new List<string>();
	for (int i = 1; i <= 100; i++)
	{
	urls.Add($"https://jsonplaceholder.typicode.com/users/{i}");
	}
	
	Console.WriteLine("开始批量请求...");
	var stopwatch = System.Diagnostics.Stopwatch.StartNew();
	
	// 同步请求(慢!100个请求要等100秒)
	// foreach (var url in urls)
	// {
	// var response = await _httpClient.GetAsync(url);
	// response.EnsureSuccessStatusCode();
	// }
	
	// 异步请求(快!100个请求只花5秒左右)
	var tasks = new List<Task>();
	foreach (var url in urls)
	{
	// 不要在这里await,先把所有任务都启动
	tasks.Add(FetchUrlAsync(url));
	}
	// 等待所有任务完成
	await Task.WhenAll(tasks);
	
	stopwatch.Stop();
	Console.WriteLine($"批量请求完成,耗时:{stopwatch.ElapsedMilliseconds}毫秒");
	}
	
	// 异步请求单个URL
	private static async Task FetchUrlAsync(string url)
	{
	try
	{
	var response = await _httpClient.GetAsync(url);
	response.EnsureSuccessStatusCode();
	string content = await response.Content.ReadAsStringAsync();
	Console.WriteLine($"请求成功:{url}");
	}
	catch (Exception ex)
	{
	Console.WriteLine($"请求失败:{url},错误:{ex.Message}");
	}
	}
	}

代码逐行讲:
1.private static readonly HttpClient _httpClient = new HttpClient();:全局单例HttpClient,避免每次请求都创建新实例导致端口耗尽;
2.var tasks = new List();:创建任务列表,用来存放所有异步请求任务;
3.tasks.Add(FetchUrlAsync(url));:启动异步请求任务,但不等待它完成——这样所有请求会同时发送,而不是一个接一个;
4.await Task.WhenAll(tasks);:等待所有任务完成,这是异步批量请求的核心;
5.FetchUrlAsync:异步请求单个URL,内部用await处理IO操作,不会阻塞线程。
性能对比:同步请求100个URL需要约100秒(每个请求约1秒),异步请求只需要约5秒(所有请求同时发送,并行处理)。
2. 异步HTTP服务器:用ASP.NET Core实现高并发
ASP.NET Core是天生异步的,所有控制器方法都可以写成异步的,能轻松支撑上万并发请求。
示例:异步控制器方法
csharp

	using Microsoft.AspNetCore.Mvc;
	using System.Net.Http;
	using System.Threading.Tasks;
	
	namespace AsyncWebApi.Controllers;
	
	[ApiController]
	[Route("api/[controller]")]
	public class UsersController : ControllerBase
	{
	private readonly IHttpClientFactory _httpClientFactory;
	
	// 构造函数注入IHttpClientFactory
	public UsersController(IHttpClientFactory httpClientFactory)
	{
	_httpClientFactory = httpClientFactory;
	}
	
	// 异步控制器方法:返回Task<IActionResult>
	[HttpGet("{id}")]
	public async Task<IActionResult> GetUser(int id)
	{
	var httpClient = _httpClientFactory.CreateClient();
	string url = $"https://jsonplaceholder.typicode.com/users/{id}";
	var response = await httpClient.GetAsync(url);
	response.EnsureSuccessStatusCode();
	var user = await response.Content.ReadFromJsonAsync<User>();
	return Ok(user);
	}
	
	public class User
	{
	public int Id { get; set; }
	public string Name { get; set; } = string.Empty;
	public string Email { get; set; } = string.Empty;
	}
	}

代码关键:
控制器方法返回Task,用async/await处理异步操作;
用IHttpClientFactory创建HttpClient,避免端口耗尽;
ASP.NET Core会自动管理线程池,异步方法不会阻塞线程,能支撑高并发。
五、异步WebSocket编程:实时通信的高并发处理
WebSocket是双向通信,异步编程能让服务器同时处理上万客户端的连接,而不会阻塞线程。

  1. 异步WebSocket服务器:用ASP.NET Core实现
    ASP.NET Core的WebSocket支持异步操作,能轻松处理高并发客户端。
    步骤1:配置WebSocket中间件
    在Program.cs中添加WebSocket中间件:
    csharp
	var builder = WebApplication.CreateBuilder(args);
	var app = builder.Build();
	
	// 配置WebSocket中间件
	app.UseWebSockets();
	
	// 配置WebSocket端点
	app.Map("/ws", async context =>
	{
	// 检查是否是WebSocket请求
	if (!context.WebSockets.IsWebSocketRequest)
	{
	context.Response.StatusCode = StatusCodes.Status400BadRequest;
	return;
	}
	
	// 接受WebSocket连接
	using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
	Console.WriteLine("客户端已连接");
	
	var buffer = new byte[1024 * 4]; // 4KB缓冲区
	WebSocketReceiveResult result;
	
	// 异步接收客户端消息
	do
	{
	result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
	// 把收到的消息原封不动发回去(回声服务器)
	await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);
	Console.WriteLine($"收到消息:{System.Text.Encoding.UTF8.GetString(buffer, 0, result.Count)}");
	} while (!result.CloseStatus.HasValue);
	
	// 关闭WebSocket连接
	await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
	Console.WriteLine("客户端已断开");
	});
	
	app.Run("http://localhost:5000");

代码逐行讲:
1.app.UseWebSockets();:启用WebSocket中间件;
2.app.Map("/ws", async context => { ... });:配置WebSocket端点,客户端需要连接ws://localhost:5000/ws;
3.await context.WebSockets.AcceptWebSocketAsync();:异步接受WebSocket连接,返回WebSocket对象;
4.await webSocket.ReceiveAsync(...);:异步接收客户端消息,ReceiveAsync会在收到消息时返回,不会阻塞线程;
5.await webSocket.SendAsync(...);:异步发送消息给客户端;
6.do-while循环:持续接收客户端消息,直到客户端关闭连接。
2. 异步WebSocket客户端:用WebSocketSharp实现
WebSocketSharp也支持异步操作,客户端可以异步收发消息。
示例:异步WebSocket客户端
csharp

	using System;
	using System.Threading.Tasks;
	using WebSocketSharp;
	
	namespace AsyncWebSocketClient;
	
	class Program
	{
	static async Task Main(string[] args)
	{
	var ws = new WebSocket("ws://localhost:5000/ws");
	
	// 异步连接服务器
	await ws.ConnectAsync();
	Console.WriteLine("已连接到WebSocket服务器");
	
	// 异步发送消息
	await ws.SendAsync("你好,服务器!");
	Console.WriteLine("已发送消息");
	
	// 异步接收消息
	var result = await ws.ReceiveAsync();
	string message = System.Text.Encoding.UTF8.GetString(result.RawData);
	Console.WriteLine($"收到服务器消息:{message}");
	
	// 异步关闭连接
	await ws.CloseAsync(WebSocketSharp.CloseStatusCode.Normal, "主动退出");
	Console.WriteLine("已断开连接");
	}
	}

六、异步编程的坑:别踩这些雷

  1. 不要在异步方法里用Task.Wait()或Task.Result
    这会导致线程死锁——异步方法会把上下文(比如UI线程上下文)保存下来,Task.Wait()会阻塞当前线程,而异步方法需要上下文才能继续执行,结果就是互相等,死锁。
    错误示例:
    csharp
	// 错误:会导致死锁
	public void SyncMethod()
	{
	var result = AsyncMethod().Result;
	}
	
	public async Task<string> AsyncMethod()
	{
	await Task.Delay(1000);
	return "Hello";
	}

正确示例:
csharp

	// 正确:用await调用异步方法
	public async Task SyncMethod()
	{
	var result = await AsyncMethod();
	}
  1. 不要忽略异步方法的返回值
    如果异步方法返回Task,不要直接调用它而不await,否则会导致异常无法捕获,资源无法释放。
    错误示例:
    csharp
	// 错误:忽略返回值,异常无法捕获
	AsyncMethod();
	
	public async Task AsyncMethod()
	{
	await Task.Delay(1000);
	throw new Exception("错误");
	}

正确示例:
csharp

	// 正确:await异步方法,捕获异常
	try
	{
	await AsyncMethod();
	}
	catch (Exception ex)
	{
	Console.WriteLine($"异常:{ex.Message}");
	}
  1. 不要在循环里用await(除非你想串行执行)
    如果在循环里用await,会导致异步方法串行执行,失去异步的优势——应该把所有任务都启动,然后用Task.WhenAll等待。
    错误示例:
    csharp
	// 错误:串行执行,和同步一样慢
	foreach (var url in urls)
	{
	await FetchUrlAsync(url);
	}

正确示例:
csharp

	// 正确:并行执行,效率高
	var tasks = urls.Select(url => FetchUrlAsync(url)).ToList();
	await Task.WhenAll(tasks);

七、基础知识拓展

  1. Task vs ValueTask
    Task:引用类型,适合大部分场景,有一定的内存分配开销;
    ValueTask:值类型,适合高性能场景(比如异步方法经常同步完成,不需要分配Task对象);
    什么时候用ValueTask:当异步方法的完成时间非常短(比如从缓存中读取数据),或者大部分情况下是同步完成的,用ValueTask可以减少内存分配,提高性能。
    示例:
    csharp
	public async ValueTask<string> GetCachedValueAsync(string key)
	{
	// 先从缓存中读取,如果有就直接返回(同步完成)
	if (_cache.TryGetValue(key, out string value))
	{
	return value;
	}
	// 缓存中没有,从数据库读取(异步完成)
	value = await _db.GetValueAsync(key);
	_cache.Add(key, value);
	return value;
	}
  1. CancellationToken:取消异步操作
    CancellationToken用来取消异步操作,比如用户点击取消按钮,或者超时。
    示例:
    csharp
	// 创建CancellationTokenSource,设置超时时间为5秒
	var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
	
	try
	{
	var response = await _httpClient.GetAsync(url, cts.Token);
	response.EnsureSuccessStatusCode();
	}
	catch (OperationCanceledException ex)
	{
	Console.WriteLine("请求超时或被取消");
	}
  1. 异步流:IAsyncEnumerable
    C# 8.0引入了IAsyncEnumerable,用来异步返回多个结果,适合分页获取数据、实时流数据等场景。
    示例:异步流返回用户列表
    csharp
	public async IAsyncEnumerable<User> GetUsersAsync()
	{
	int page = 1;
	while (true)
	{
	var users = await _db.GetUsersAsync(page, 10);
	if (users.Count == 0)
	{
	break;
	}
	foreach (var user in users)
	{
	yield return user;
	}
	page++;
	}
	}
	
	// 调用异步流
	await foreach (var user in GetUsersAsync())
	{
	Console.WriteLine(user.Name);
	}

八、总结:异步是网络编程的“必选项”
异步的核心:不要让CPU等IO,让线程在等IO时去做其他事情;
异步HTTP:用HttpClient的异步方法,结合Task.WhenAll实现批量请求,用ASP.NET Core写异步服务器;
异步WebSocket:用ASP.NET Core的WebSocket中间件,异步收发消息,支撑高并发客户端;
避坑指南:不要用Task.Wait()或Task.Result,不要忽略异步方法的返回值,不要在循环里串行await;
进阶:用ValueTask提高性能,用CancellationToken取消异步操作,用IAsyncEnumerable处理异步流。


 本站原创,转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49524.html


相关教程