第二章:C#异步编程简介
第二章:异步编程简介 🚀
异步编程(Asynchronous Programming)是现代编程中处理 I/O 密集型任务的关键技术之一。与传统的同步编程不同,异步编程允许程序在等待耗时操作(如网络请求、文件读写、数据库查询等)完成时,继续执行其他任务,从而提高应用程序的响应性和效率。
在本章中,我们将介绍异步编程的基本概念、C# 中的异步编程模型以及常用的 async
和 await
关键字。我们还会探讨异步编程的一些常见应用场景和最佳实践,帮助你理解如何在实际开发中使用异步编程来提升性能。
2.1 异步编程的基本概念 🤔
同步 vs 异步
- 同步编程 🛑:在同步编程中,任务是顺序执行的。如果某个操作是耗时的(如网络请求),程序会阻塞等待操作完成,之后才会继续执行下一行代码。这种方式简单直观,但在处理 I/O 密集型任务时,可能会导致程序变得不响应。
- 异步编程 🔄:异步编程允许程序在执行耗时操作时,不必等待操作完成,而是立即返回继续执行其他任务。耗时操作完成后,会通过回调、事件或
Task
通知程序结果。这样,程序不会被阻塞,能更好地利用系统资源。
异步编程的优势 🌟
- 提高性能和吞吐量:异步编程避免了线程的阻塞,使得应用程序可以处理更多的任务。
- 提升用户体验:对于 GUI 应用程序,异步编程能保持界面的响应性,避免“卡顿”现象。
- 更好地利用资源:异步编程通常使用线程池来管理后台任务,避免了频繁创建和销毁线程的开销。
2.2 C# 中的异步编程模型简介 🛠️
C# 提供了多种方式来实现异步编程,其中最常用的是基于 Task
和 async/await
的异步编程模型。在此之前,C# 中还有以下异步编程模型:
异步编程模型(APM)
APM(Asynchronous Programming Model) 是 C# 中最早引入的异步编程模式,通常使用 BeginXXX 和 EndXXX 方法来表示异步操作。BeginXXX 方法启动异步操作,而 EndXXX 方法用于获取操作结果。APM 使用 回调 或 IAsyncResult 来处理异步任务的完成。虽然简单,但代码往往难以阅读和维护,容易形成“回调地狱”。
基于事件的异步模式(EAP)
EAP(Event-based Asynchronous Pattern,EAP)是一种较早使用的异步编程模式。它主要用于处理需要长时间运行的操作(例如 I/O 操作),并通过事件通知调用者操作的完成情况。这种模型曾经在 .NET Framework 2.0 和 3.5 中被广泛使用,随着 async/await
引入后,EAP 逐渐被淘汰。
基于任务的异步模式(TAP)
C# 5.0 引入了 Task
和 async/await
,这是一种更加现代和简洁的异步编程方式,以同步编码的方式编写异步代码。
async
和 await
关键字 🔑
async
:用于标记一个方法为异步方法。异步方法通常返回Task
或Task<T>
,表示异步操作的结果。await
:用于等待一个异步操作的完成,并在操作完成后继续执行后续代码。await
会释放当前线程,让它去处理其他任务,避免了阻塞。
APM
和EAP
基本已经被淘汰了,在本系列文章后续也不会再过多介绍。详情参考官网
示例代码
基于回调(Callback)异步编程模型(APM) 🧩
回调 是一种简单的异步编程方式,通常通过委托或匿名方法来实现。开发者将一个方法作为参数传递给异步操作,当操作完成时,调用此方法返回结果。
using System; using System.Threading; class Program { static void Main(string[] args) { Console.WriteLine("开始异步操作..."); PerformAsyncOperation(ResultCallback); Console.WriteLine("主线程继续执行..."); // 防止程序过早结束 Thread.Sleep(3000); } // 模拟异步操作 static void PerformAsyncOperation(Action<string> callback) { new Thread(() => { Thread.Sleep(2000); // 模拟耗时操作 callback("操作完成,结果为:42"); }).Start(); } // 回调方法 static void ResultCallback(string result) { Console.WriteLine(result); } }
输出:
开始异步操作... 主线程继续执行... 操作完成,结果为:42
解释:
PerformAsyncOperation
方法启动了一个新线程,并在操作完成后调用回调方法ResultCallback
。- 主线程不会被阻塞,能够继续执行其他操作。
- 回调方法用于处理异步操作的结果。
回调地狱(Callback Hell)🔥
回调地狱是指当多个异步操作依次依赖回调时,代码会形成嵌套的结构,导致难以维护和阅读。这种问题在复杂场景中尤为常见。
回调地狱示例
using System; using System.Threading; class Program { static void Main(string[] args) { Console.WriteLine("开始异步任务链..."); Step1(result1 => { Console.WriteLine(result1); Step2(result2 => { Console.WriteLine(result2); Step3(result3 => { Console.WriteLine(result3); Console.WriteLine("所有步骤完成!"); }); }); }); // 防止程序过早结束 Thread.Sleep(5000); } static void Step1(Action<string> callback) { new Thread(() => { Thread.Sleep(1000); callback("步骤 1 完成"); }).Start(); } static void Step2(Action<string> callback) { new Thread(() => { Thread.Sleep(1000); callback("步骤 2 完成"); }).Start(); } static void Step3(Action<string> callback) { new Thread(() => { Thread.Sleep(1000); callback("步骤 3 完成"); }).Start(); } }
输出:
开始异步任务链... 步骤 1 完成 步骤 2 完成 步骤 3 完成 所有步骤完成!
问题:
- 嵌套的回调导致代码呈现“金字塔”结构,难以阅读和维护。
- 异常处理复杂,容易遗漏错误处理逻辑。
基于事件的异步模式(EAP) 📅
EAP 是 C# 中较早的一种异步编程模型,它使用 事件 来通知异步操作的完成状态。EAP 模式通常以 BeginXXX
和 EndXXX
命名方法,或者通过事件处理完成通知。
示例代码:WebClient
的 EAP 模式
using System; using System.Net; class Program { static void Main(string[] args) { using (WebClient client = new WebClient()) { client.DownloadStringCompleted += OnDownloadStringCompleted; Console.WriteLine("开始下载数据..."); client.DownloadStringAsync(new Uri("https://www.example.com")); // 防止程序过早结束 Console.ReadLine(); } } // 下载完成时触发的事件处理程序 static void OnDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e) { if (e.Error != null) { Console.WriteLine($"下载失败:{e.Error.Message}"); return; } Console.WriteLine("下载成功,数据长度:" + e.Result.Length); } }
输出:
开始下载数据... 下载成功,数据长度:1270
解释:
DownloadStringAsync
方法启动异步下载操作。- 当下载完成后,会触发
DownloadStringCompleted
事件,并调用事件处理程序OnDownloadStringCompleted
。 e.Result
包含下载的数据结果。
EAP 的问题
- 复杂性:每个异步操作都需要定义事件和处理程序,增加了代码的复杂性。
- 错误处理困难:异常处理需要在事件处理程序中显式检查
Error
属性。 - 可维护性差:对于大量异步操作,代码的结构会变得凌乱。
基于任务的异步模式(TAP)
使用 async/await
重写回调示例
using System; using System.Threading.Tasks; class Program { static async Task Main(string[] args) { Console.WriteLine("开始异步任务链..."); await Step1(); await Step2(); await Step3(); Console.WriteLine("所有步骤完成!"); } static async Task Step1() { await Task.Delay(1000); Console.WriteLine("步骤 1 完成"); } static async Task Step2() { await Task.Delay(1000); Console.WriteLine("步骤 2 完成"); } static async Task Step3() { await Task.Delay(1000); Console.WriteLine("步骤 3 完成"); } }
优势:
- 代码结构更加清晰,不再有深层嵌套。
- 异常处理可以使用标准的
try/catch
块。 - 异步方法的调用和同步方法类似,降低了编写和理解异步代码的难度。
解释:
GetDataAsync
方法被标记为async
,表示这是一个异步方法。await
关键字等待GetStringAsync
方法完成,而不会阻塞主线程。- 当网络请求完成后,
response
字符串会返回给调用者,程序继续执行。
小结:为什么选择 async/await
? 💡
随着 C# 5.0 引入 async/await
关键字,异步编程变得更加简洁和直观。相比于回调和 EAP 模式,async/await
提供了更高层次的抽象,能够以类似于同步代码的方式编写异步逻辑,极大地提升了代码的可读性和维护性。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· DeepSeek “源神”启动!「GitHub 热点速览」
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器