C#中的Async和Await:拯救应用程序免于“卡死”
\n\n您是否曾经运行过一个C#程序,点击按钮后界面“冻结”了几秒钟?或者,您的服务器在处理一个“繁重”请求时,停止响应新请求?熟悉吗?这是同步代码的经典问题——执行线程在等待操作(如读取文件、数据库查询、HTTP调用)完成时被阻塞。
\n\n这个问题的解决方案是异步编程。在C#中,主要的工具是关键字async和await。如果您曾经问过自己:“如何以及何时使用async和await?”——您来对地方了。在本教程中,我们将从基础到最佳实践逐一讲解,帮助您编写快速、响应灵敏且可扩展的代码。
本材料基于Stack Overflow上一个真实的热门问题,该问题获得了数千次浏览。我们将其改编为教育内容,并添加了代码示例和清晰的解释。
\n\n什么是async和await?简单来说
\n\n想象一下,您是一位主厨(您的程序)。您需要准备晚餐。其中一道菜是汤,需要煮30分钟。在同步世界里,您会站在炉子旁,只是等着汤煮好,什么也不做。很傻,对吧?
\n\n异步就是您把汤放上去煮(async),不等它煮好,就开始切沙拉、摆桌子、做其他事情。当汤煮好时,您再回来处理它(await)。您没有浪费时间——您很高效。
- \n
async—— 方法的修饰符,告诉编译器:“此方法包含异步操作。当我遇到await时,不要阻塞线程。” \n await—— 应用于任务(Task)的操作符。它说:“暂停此方法的执行,直到任务完成,但不要阻塞线程。一旦任务完成,从该位置继续执行。” \n
重要提示:await不会阻塞线程。它将控制权返回给调用代码,而线程继续执行其他工作。
何时使用async和await?
\n\n这是最常见的问题。答案很简单:每当您执行可能需要很长时间的操作(I/O密集型操作),并且您不希望阻塞线程时。
\n\n以下是典型场景:
\n- \n
- 网络请求: HTTP调用(HttpClient)、WebSocket操作。 \n
- 文件操作: 读取/写入大文件、流操作。 \n
- 数据库查询: Entity Framework、ADO.NET、Dapper。 \n
- Web服务操作: WCF、REST API。 \n
- 任何输入/输出(I/O)操作: 访问磁盘、网络、打印机。 \n
何时不应该使用async和await?
\n- \n
- CPU密集型操作: 如果您的任务需要大量计算(3D渲染、视频处理、复杂数学计算),
async/await没有帮助。这里应该使用Task.Run或Parallel来在多个CPU核心上并行处理。 \n - 在类构造函数中: 构造函数不能是async。请使用工厂方法(
public static async Task<MyClass> CreateAsync())。 \n - 在属性的getter/setter中: 它们也不能是async。请使用方法。 \n
它是如何工作的?通过代码示例分析
\n\n让我们看一段实际代码。假设我们从远程服务器加载数据。
\n\n糟糕的示例(同步)
\npublic string GetDataFromWeb()\n{\n usi