在 C# 中,async
和 await
主要用于编写非阻塞异步代码,提升程序的响应性和资源利用率。
执行I/O密集型操作,比如文件读写、网络请求或者数据库访问。这些操作通常会等待外部资源,使用异步可以释放线程池线程,提高应用程序的响应性。比如说,如果一个Web服务器处理很多请求,同步方法会导致线程被阻塞,而异步可以让线程处理其他请求,提升吞吐量。
还有UI应用程序,比如WPF或WinForms,长时间运行的任务如果不异步,会导致界面卡顿。使用async可以让后台任务运行,同时保持UI响应。例如,下载文件时,如果同步下载,用户界面就会冻结,但异步的话用户还能操作界面。
应用场景:
适用场景:涉及等待外部资源(不占用 CPU 时间)的操作,如:
- 文件读写(
FileStream.ReadAsync
) - 网络请求(HTTP API 调用、数据库查询)
- 流处理(如图片加载)
异步操作会释放调用线程(如 UI 线程或线程池线程),避免阻塞。例如,Web 服务器通过异步处理可同时服务更多请求。
public async Task<string> DownloadDataAsync()
{
using var client = new HttpClient();
return await client.GetStringAsync("https://example.com"); // 非阻塞等待
}
2. 保持 UI 响应性
适用场景:在桌面或移动应用的 UI 线程中执行耗时操作时(如按钮点击事件)。
异步操作防止界面冻结。例如,文件下载期间用户仍可交互。
private async void DownloadButton_Click(object sender, EventArgs e)
{
StatusLabel.Text = "Downloading...";
await Task.Delay(3000); // 模拟耗时操作(如下载)
StatusLabel.Text = "Done!"; // UI 更新自动回到主线程
}
3. 后端服务高并发
适用场景:服务器应用(如 ASP.NET Core)处理大量并发请求。
异步方法释放线程池线程,使服务器能处理更多请求,避免线程饥饿。
public async Task<IActionResult> GetData()
{
var data = await _dbContext.Users.FromSqlRawAsync("SELECT ..."); // 异步数据库查询
return Ok(data);
}
4. CPU 密集型操作的特殊处理
适用场景:长时间运行的计算任务(如图像处理、复杂算法)。
需结合 Task.Run
将 CPU 工作转移到线程池,避免阻塞主线程(尤其在 UI 应用中)。
public async Task ProcessImageAsync()
{
await Task.Run(() =>
{
// 耗时的 CPU 计算
ApplyFilter(image);
});
// 回到 UI 线程更新结果
}
何时不需要用 async
- 简单/同步操作:纯内存操作(如循环、数学计算)无需异步。
- 强制要求同步的 API:某些旧库或框架可能不支持异步。
关键原则
- 从顶层开始:入口点(如
Main
、控制器 Action)支持async
(C# 7.1+)。 - 避免
async void
:仅限事件处理,优先用Task
返回值以便错误处理。 - 注意异常传播:异步方法异常会被封装在
AggregateException
或直接抛出(取决于调用方式)。 - 不要滥用
ConfigureAwait(false)
:在库代码中可减少上下文切换开销,但 UI 应用需保留上下文。