原因
WinForm 采用单线程模型,所有 UI 控件必须在主线程(也叫 UI 线程)上更新。如果后台操作(如数据采集、处理)耗时较长,可能会导致界面卡顿或无响应。
解决方案
使用 BackgroundWorker
BackgroundWorker 是 WinForm 提供的一个用于处理后台操作的类。它可以在后台线程上运行代码,同时在 UI 线程上更新界面。
- 新建按钮改名为 asyncButton
- 新建 label 改名为 resultLabel
1private BackgroundWorker backgroundWorker;2
3public MainForm()4{5 InitializeComponent();6
7 backgroundWorker = new BackgroundWorker();8 backgroundWorker.DoWork += BackgroundWorker_DoWork;9 backgroundWorker.RunWorkerCompleted += BackgroundWorker_RunWorkerCompleted;10}11
12private void asyncButton_Click(object sender, EventArgs e)13{14 if (!backgroundWorker.IsBusy)15 {23 collapsed lines
16 backgroundWorker.RunWorkerAsync();17 }18}19
20private void BackgroundWorker_DoWork(object? sender, DoWorkEventArgs e)21{22 Thread.Sleep(2000);23 // 将结果存储在e.Result中24 e.Result = "耗时两秒的任务处理完成,中间界面不出现卡死。";25}26
27private void BackgroundWorker_RunWorkerCompleted(object? sender, RunWorkerCompletedEventArgs e)28{29 if (e.Error != null)30 {31 MessageBox.Show("Error: " + e.Error.Message);32 }33 else34 {35 if (e.Result is string)36 resultLabel.Text = e.Result as string;37 }38}使用 Task 和 async/await
- 新建按钮改名为 taskButton
1private async void taskButton_Click(object sender, EventArgs e)2{3 resultLabel.Text = "耗时任务开始!";4
5 var result = await Task.Run<string>(() =>6 {7 Thread.Sleep(2000);8 return "耗时两秒的任务处理完成,中间界面不出现卡死。";9 });10
11 resultLabel.Text = result;12}使用 Thread
直接使用 Thread 类来创建后台线程,需要注意线程间的通信问题,尤其是 UI 线程的更新需要使用 Invoke 方法。
- 新建按钮改名为 threadButton
1private void threadButton_Click(object sender, EventArgs e)2{3 resultLabel.Text = "耗时任务开始!";4
5 Thread thread = new Thread(() =>6 {7 Thread.Sleep(2000);8 this.Invoke(new Action(() =>9 {10 resultLabel.Text = "耗时两秒的任务处理完成,中间界面不出现卡死。";11 }));12 });13 thread.Start();14}使用 Task 并结合 Progress<T>
- 新建 ProgressBar 进度条控件改名为 progressBar
- 新建按钮改名为 progressButton
1private async void progressButton_Click(object sender, EventArgs e)2{3 var progress = new Progress<int>(value =>4 {5 // 在UI线程上更新进度6 progressBar.Value = value;7 });8
9 await Task.Run(() =>10 {11 for (int i = 0; i <= 100; i++)12 {13 // 这里不直接调用 progressBar 是因为Task内已经是在另一个线程上了14 // progressBar 控件是在UI线程创建的,其他线程不能直接访问15 // progressBar.Value = i;5 collapsed lines
16 (progress as IProgress<int>).Report(i);17 Thread.Sleep(50); // 模拟耗时操作18 }19 });20}