在现代C#网络应用开发中,异步编程已经从"可选技能"变成了"必备技能"。无论是开发Web API、实时通讯应用还是高性能服务,异步编程都能显著提升应用程序的响应性和资源利用率。本文将深入剖析C#中的异步编程模型(async/await),并结合网络编程场景提供详细示例。
在传统的同步编程模型中,代码按顺序执行,当遇到耗时操作时(如网络请求),线程会被阻塞直到操作完成:
C#// 同步方法示例
public string DownloadWebPage(string url)
{
// 此处线程被阻塞,直到下载完成
WebClient client = new WebClient();
return client.DownloadString(url); // 阻塞操作
}
而异步编程允许线程在等待耗时操作完成时去执行其他工作:
C#// 异步方法示例
public async Task<string> DownloadWebPageAsync(string url)
{
// 异步操作,不会阻塞线程
WebClient client = new WebClient();
return await client.DownloadStringTaskAsync(url); // 非阻塞操作
}
C#的async/await模型是基于Task和Task类型构建的语法糖,大大简化了异步编程。
C#// 无返回值的异步方法
public async Task ProcessDataAsync()
{
// 异步操作
await Task.Delay(1000); // 模拟耗时操作
Console.WriteLine("处理完成");
}
// 有返回值的异步方法
public async Task<int> CalculateAsync()
{
await Task.Delay(1000); // 模拟耗时计算
return 42; // 返回计算结果
}
以下是一个完整的HTTP请求示例,展示了如何使用异步方法进行网络请求:
C#namespace AppNetworkAsync
{
internal class Program
{
// 创建一个具有自定义配置的HttpClient
private static readonly HttpClient httpClient = CreateHttpClient();
public static async Task Main()
{
try
{
// 调用异步方法并等待结果
string result = await FetchDataAsync("https://push2.eastmoney.com/api/qt/clist/get?np=1&fltt=1&invt=2&cb=jQuery37105169177110964975_1746321786227&fs=m%3A1%2Bt%3A2%2Cm%3A1%2Bt%3A23&fields=f12%2Cf13%2Cf14%2Cf1%2Cf2%2Cf4%2Cf3%2Cf152%2Cf5%2Cf6%2Cf7%2Cf15%2Cf18%2Cf16%2Cf17%2Cf10%2Cf8%2Cf9%2Cf23&fid=f3&pn=1&pz=20&po=1&dect=1&ut=fa5fd1943c7b386f172d6893dbfba10b&wbp2u=%7C0%7C0%7C0%7Cweb&_=1746321786242");
Console.WriteLine($"获取的数据: {result}");
// 异步并行请求示例
Task<string> task1 = FetchDataAsync("https://push2.eastmoney.com/api/qt/clist/get?np=1&fltt=1&invt=2&cb=jQuery37105169177110964975_1746321786227&fs=m%3A1%2Bt%3A2%2Cm%3A1%2Bt%3A23&fields=f12%2Cf13%2Cf14%2Cf1%2Cf2%2Cf4%2Cf3%2Cf152%2Cf5%2Cf6%2Cf7%2Cf15%2Cf18%2Cf16%2Cf17%2Cf10%2Cf8%2Cf9%2Cf23&fid=f3&pn=2&pz=20&po=1&dect=1&ut=fa5fd1943c7b386f172d6893dbfba10b&wbp2u=%7C0%7C0%7C0%7Cweb&_=1746321786242");
Task<string> task2 = FetchDataAsync("https://push2.eastmoney.com/api/qt/clist/get?np=1&fltt=1&invt=2&cb=jQuery37105169177110964975_1746321786227&fs=m%3A1%2Bt%3A2%2Cm%3A1%2Bt%3A23&fields=f12%2Cf13%2Cf14%2Cf1%2Cf2%2Cf4%2Cf3%2Cf152%2Cf5%2Cf6%2Cf7%2Cf15%2Cf18%2Cf16%2Cf17%2Cf10%2Cf8%2Cf9%2Cf23&fid=f3&pn=3&pz=20&po=1&dect=1&ut=fa5fd1943c7b386f172d6893dbfba10b&wbp2u=%7C0%7C0%7C0%7Cweb&_=1746321786242");
// 等待所有请求完成
await Task.WhenAll(task1, task2);
Console.WriteLine($"第一个API结果: {task1.Result}");
Console.WriteLine($"第二个API结果: {task2.Result}");
}
catch (Exception ex)
{
Console.WriteLine($"发生错误: {ex.Message}");
}
}
// 创建配置好的HttpClient
private static HttpClient CreateHttpClient()
{
var client = new HttpClient();
// 设置默认请求头,模拟浏览器
client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36");
client.DefaultRequestHeaders.Add("Accept", "application/json,text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
client.DefaultRequestHeaders.Add("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8");
client.DefaultRequestHeaders.Add("Referer", "https://finance.pae.baidu.com/");
// 设置超时时间
client.Timeout = TimeSpan.FromSeconds(30);
return client;
}
// 异步获取网络数据的方法
public static async Task<string> FetchDataAsync(string url)
{
Console.WriteLine($"开始请求URL: {url}");
try
{
// 创建请求消息
var request = new HttpRequestMessage(HttpMethod.Get, url);
// 发送异步HTTP请求,并使用超时令牌
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)))
{
HttpResponseMessage response = await httpClient.SendAsync(request, cts.Token);
// 确保HTTP请求成功
response.EnsureSuccessStatusCode();
// 异步读取响应内容
string content = await response.Content.ReadAsStringAsync();
Console.WriteLine($"完成请求URL: {url}");
return content;
}
}
catch (TaskCanceledException)
{
Console.WriteLine($"请求URL超时: {url}");
throw new TimeoutException($"请求URL超时: {url}");
}
catch (Exception ex)
{
Console.WriteLine($"请求URL出错: {url}, 错误: {ex.Message}");
throw;
}
}
}
}

遵循微软推荐的命名约定,在异步方法名后添加"Async"后缀:
C#// 正确的命名
public async Task<string> DownloadFileAsync(string url)
{
// 异步实现
}
// 不推荐的命名
public async Task<string> DownloadFile(string url)
{
// 异步实现
}
C#// 错误示例:在异步方法中使用同步调用
public async Task ProcessDataAsync()
{
// 错误:在异步方法中使用同步阻塞调用
string data = GetDataSync(); // 同步方法
await ProcessAsync(data);
}
// 正确示例
public async Task ProcessDataAsync()
{
// 正确:全程使用异步调用
string data = await GetDataAsync();
await ProcessAsync(data);
}
C#public async Task DownloadAndProcessAsync(string url)
{
try
{
var data = await DownloadAsync(url);
await ProcessAsync(data);
}
catch (HttpRequestException ex)
{
// 处理网络异常
Console.WriteLine($"下载失败: {ex.Message}");
}
catch (Exception ex)
{
// 处理其他异常
Console.WriteLine($"处理失败: {ex.Message}");
}
}
C# 8.0引入的异步流(IAsyncEnumerable)适用于处理大量数据:
C#using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
namespace AppNetworkAsync
{
internal class Program
{
public static async Task Main()
{
try
{
// 正确消费异步流
await foreach (var stockName in GetLargeDataSetAsync())
{
Console.WriteLine(stockName);
}
Console.WriteLine("所有数据处理完成");
}
catch (Exception ex)
{
Console.WriteLine($"发生错误: {ex.Message}");
}
}
// 产生异步流的方法
public static async IAsyncEnumerable<string> GetLargeDataSetAsync()
{
using HttpClient client = new HttpClient();
// 模拟分页请求
for (int page = 1; page <= 10; page++)
{
Console.WriteLine($"正在获取第 {page} 页数据...");
// 异步获取每一页数据
string url = $"https://push2.eastmoney.com/api/qt/clist/get?np=1&fltt=1&invt=2&cb=jQuery37105169177110964975_1746321786227&fs=m%3A1%2Bt%3A2%2Cm%3A1%2Bt%3A23&fields=f12%2Cf13%2Cf14%2Cf1%2Cf2%2Cf4%2Cf3%2Cf152%2Cf5%2Cf6%2Cf7%2Cf15%2Cf18%2Cf16%2Cf17%2Cf10%2Cf8%2Cf9%2Cf23&fid=f3&pn={page}&pz=20&po=1&dect=1&ut=fa5fd1943c7b386f172d6893dbfba10b&wbp2u=%7C0%7C0%7C0%7Cweb&_=1746321786242";
string pageData;
try
{
pageData = await client.GetStringAsync(url);
}
catch (Exception ex)
{
Console.WriteLine($"获取数据失败: {ex.Message}");
continue; // 跳过这一页,继续下一页
}
// 在继续之前添加一点延迟,避免请求过快
await Task.Delay(500);
// 处理并返回数据项
foreach (var item in ParsePageData(pageData))
{
yield return item;
}
}
}
// 辅助方法:解析页面数据
private static IEnumerable<string> ParsePageData(string pageData)
{
List<string> results = new List<string>();
// 东方财富的数据通常是JSONP格式,需要提取JSON部分
var match = Regex.Match(pageData, @"jQuery\d+_\d+\((.*)\)");
if (!match.Success)
{
return new[] { "数据格式不符合预期" };
}
string jsonData = match.Groups[1].Value;
JsonDocument doc = null;
try
{
// 解析JSON数据
doc = JsonDocument.Parse(jsonData);
// 提取股票数据数组
if (doc.RootElement.TryGetProperty("data", out JsonElement dataElement) &&
dataElement.TryGetProperty("diff", out JsonElement diffElement))
{
foreach (JsonElement stockElement in diffElement.EnumerateArray())
{
// 只获取股票名称(f14)
if (stockElement.TryGetProperty("f14", out JsonElement nameElement))
{
string name = nameElement.GetString() ?? "未知名称";
results.Add(name);
}
}
}
}
catch (JsonException)
{
results.Add("JSON解析失败");
}
finally
{
doc?.Dispose();
}
return results;
}
}
}

C#的async/await模型为网络编程带来了显著优势:
掌握C#异步编程是现代.NET开发者的必备技能,特别是在网络编程领域。正确使用async/await模式可以显著提升应用程序的性能和用户体验。
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!