关于C#编程中的async / await关键字
一句话理解
async/await 本质是:用"同步写法"写"异步代码",避免线程被阻塞。
核心
- async:告诉编译器------这个方法里"可能会停下来等一会儿"
- await :真的"停下来等",但不占用线程
- 等待期间:线程被释放,可以去干别的事
- 等结果好了:自动从 await 后一行继续执行
👉 不是多线程魔法,而是状态机 + 回调的语法糖
最基础示例
csharp
static async Task Main()
{
Console.WriteLine("开始");
await Task.Delay(2000); // 模拟耗时操作(2秒)
Console.WriteLine("结束");
}
执行过程
- 打印「开始」
- 遇到
await
→ 方法"挂起",线程被释放 - 2 秒后恢复执行
- 打印「结束」
重点:这 2 秒内,线程没被卡死
对比:同步 vs async
❌ 同步(阻塞线程)
csharp
Thread.Sleep(2000); // 线程被卡住
✅ 异步(不阻塞)
csharp
await Task.Delay(2000); // 线程释放
模拟真实场景示例
csharp
async Task<string> DownloadAsync()
{
using var client = new HttpClient();
return await client.GetStringAsync("https://example.com");
}
调用:
csharp
string html = await DownloadAsync();
你看到的是同步写法
底层其实是 异步 + 回调 + 状态机
async / await 的 3 条铁律
- await 只能等 Task / Task
- async 方法几乎都返回 Task
- 一路 async,就一路 await(不要中途
.Result)
最容易踩的坑 ❌
csharp
var result = DownloadAsync().Result; // ❌ 可能死锁
✅ 正确写法:
csharp
var result = await DownloadAsync();
一句话总结
async/await不是让代码更快,而是让程序不卡特别适合:UI、IO、网络、数据库、上位机通信
用 .NET Framework 4.8 + WPF(.NET Framework 4.8)
核心目的就一个:直观看到"同步会卡 UI,async/await 不会"。
WPF 示例(.NET Framework 4.8)
XAML(MainWindow.xaml)
xml
<Window x:Class="WpfAsyncDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Async Demo" Height="200" Width="360">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="0,0,0,12">
<Button Width="120" Margin="0,0,12,0" Content="同步卡死" Click="BtnSync_Click"/>
<Button Width="120" Content="Async不阻塞" Click="BtnAsync_Click"/>
</StackPanel>
<TextBlock Grid.Row="1" FontSize="16" Text="{Binding Status}" />
</Grid>
</Window>
代码(MainWindow.xaml.cs)
csharp
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace WpfAsyncDemo
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
private string _status = "状态";
public string Status
{
get => _status;
set { _status = value; OnPropertyChanged(); }
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
// ❌ 同步:阻塞UI线程,窗口假死
private void BtnSync_Click(object sender, RoutedEventArgs e)
{
Status = "同步开始(UI将卡住3秒)";
Thread.Sleep(3000);
Status = "同步结束";
}
// ✅ 异步:不阻塞UI线程
private async void BtnAsync_Click(object sender, RoutedEventArgs e)
{
Status = "异步开始(UI仍然流畅)";
await Task.Delay(3000);
Status = "异步结束";
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string name = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
关键点
- WPF 的按钮点击事件都在 UI线程 跑
- Thread.Sleep / 同步IO 会把 UI线程卡住 → 窗口假死
await Task.Delay/await IO会让出 UI线程 → 窗口继续响应- 默认情况下:
await之后会回到 UI线程(所以你可以直接更新 Label/TextBlock,不用 Invoke)
进阶:模拟"真实耗时IO/计算"
你如果要做"上位机通信/数据库/文件",通常是:
- IO:直接
await(比如 HttpClient、Stream.ReadAsync、SqlCommand.ExecuteReaderAsync) - CPU计算:用
Task.Run扔到线程池
给你一个通用模板(WinForms/WPF 都能用):
csharp
private async void BtnWork_Click(object sender, EventArgs e)
{
Status = "开始干活...";
var result = await Task.Run(() =>
{
// CPU密集:放这里
Thread.Sleep(3000);
return "完成";
});
Status = result;
}