c#异步编程(async/await)
注:下文摘自ChatGPT,总结与案例都非常完善,可以快速理解并应用
0:使用场景
在winform界面程序中,在ui操作中涉及到一些耗时的等待操作,使用线程自己处理已经显得力不从心,如何能更好的实现:能等待后台线程执行完,而且不阻塞UI。c#提供如下操作,高效满足此类应用场景,c++中同样有类似的用法(类似于QT线程间通信(信号槽))
1. 使用异步编程(async/await
)
最推荐的解决方法是使用 async/await
,它可以将耗时操作放到后台线程运行,同时保持 UI 线程的响应。
示例:
场景:按钮点击后等待一个耗时任务完成,但界面仍可响应。
private async void Button_Click(object sender, EventArgs e)
{
// 显示加载状态
Button.Enabled = false;
Label.Text = "Processing...";
// 执行耗时操作
await Task.Run(() => LongRunningTask());
// 恢复 UI
Label.Text = "Done!";
Button.Enabled = true;
}
// 模拟一个耗时任务
private void LongRunningTask()
{
Thread.Sleep(5000); // 模拟耗时操作,实际中可替换为真实逻辑
}
原理:
Task.Run
将耗时操作放到线程池中运行,不阻塞主线程。await
确保任务完成后才继续执行,但不阻塞 UI 线程。
2. 使用 BackgroundWorker
BackgroundWorker
是较老的异步模式,但仍可用于一些简单场景。
示例:
private BackgroundWorker worker;
public MainForm()
{
InitializeComponent();
worker = new BackgroundWorker();
worker.DoWork += Worker_DoWork;
worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
}
private void Button_Click(object sender, EventArgs e)
{
// 显示加载状态
Button.Enabled = false;
Label.Text = "Processing...";
// 启动后台任务
worker.RunWorkerAsync();
}
private void Worker_DoWork(object sender, DoWorkEventArgs e)
{
// 在后台线程中执行耗时操作
Thread.Sleep(5000); // 模拟耗时操作
}
private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// 任务完成后更新 UI
Label.Text = "Done!";
Button.Enabled = true;
}
注意:
DoWork
方法在后台线程中运行。RunWorkerCompleted
方法在主线程(UI 线程)中运行,适合更新界面。
3. 使用 Task
和 Lambda
如果你不想使用 async/await
,可以直接使用 Task
和回调来处理任务。
示例:
private void Button_Click(object sender, EventArgs e)
{
// 显示加载状态
Button.Enabled = false;
Label.Text = "Processing...";
Task.Run(() =>
{
// 在后台线程中执行耗时操作
Thread.Sleep(5000); // 模拟耗时操作
}).ContinueWith(t =>
{
// 回到主线程更新 UI
Label.Text = "Done!";
Button.Enabled = true;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
4. 使用 Dispatcher
或 Invoke
在 WPF 中,可以使用 Dispatcher
更新 UI;在 WinForms 中,可以使用 Invoke
方法。
示例(WinForms):
private void Button_Click(object sender, EventArgs e)
{
Task.Run(() =>
{
// 耗时操作
Thread.Sleep(5000);
// 使用 Invoke 更新 UI
this.Invoke((Action)(() =>
{
Label.Text = "Done!";
Button.Enabled = true;
}));
});
// 立即禁用按钮(UI 线程)
Button.Enabled = false;
Label.Text = "Processing...";
}
5. 使用 Progress<T>
(可选,报告进度)
如果耗时任务需要报告进度,可以使用 IProgress<T>
和 Progress<T>
。
示例:
private async void Button_Click(object sender, EventArgs e)
{
var progress = new Progress<int>(value =>
{
// 更新进度条
ProgressBar.Value = value;
});
// 显示加载状态
Button.Enabled = false;
Label.Text = "Processing...";
// 执行耗时任务并报告进度
await Task.Run(() => LongRunningTaskWithProgress(progress));
// 恢复 UI
Label.Text = "Done!";
Button.Enabled = true;
}
private void LongRunningTaskWithProgress(IProgress<int> progress)
{
for (int i = 0; i <= 100; i += 10)
{
Thread.Sleep(500); // 模拟任务
progress.Report(i); // 报告进度
}
}
优点:
- 允许在任务进行过程中更新 UI(如进度条)。
6. 注意事项
- 避免直接使用
Thread.Sleep
在主线程中运行:会导致 UI 完全无响应。 - 耗时操作不要在 UI 线程中运行:始终将耗时逻辑放到后台线程。
- 推荐使用现代的
async/await
:代码更简洁且易于维护。