UI线程阻塞问题

原因

WinForm采用单线程模型,所有UI控件必须在主线程(也叫UI线程)上更新。如果后台操作(如数据采集、处理)耗时较长,可能会导致界面卡顿或无响应。

解决方案

使用 BackgroundWorker

BackgroundWorker 是WinForm提供的一个用于处理后台操作的类。它可以在后台线程上运行代码,同时在UI线程上更新界面。

  • 新建按钮改名为 asyncButton
  • 新建label 改名为 resultLabel
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
private BackgroundWorker backgroundWorker;

public MainForm()
{
InitializeComponent();

backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += BackgroundWorker_DoWork;
backgroundWorker.RunWorkerCompleted += BackgroundWorker_RunWorkerCompleted;
}

private void asyncButton_Click(object sender, EventArgs e)
{
if (!backgroundWorker.IsBusy)
{
backgroundWorker.RunWorkerAsync();
}
}

private void BackgroundWorker_DoWork(object? sender, DoWorkEventArgs e)
{
Thread.Sleep(2000);
// 将结果存储在e.Result中
e.Result = "耗时两秒的任务处理完成,中间界面不出现卡死。";
}

private void BackgroundWorker_RunWorkerCompleted(object? sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
MessageBox.Show("Error: " + e.Error.Message);
}
else
{
if (e.Result is string)
resultLabel.Text = e.Result as string;
}
}

使用 Taskasync/await

  • 新建按钮改名为 taskButton
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    private async void taskButton_Click(object sender, EventArgs e)
    {
    resultLabel.Text = "耗时任务开始!";

    var result = await Task.Run<string>(() =>
    {
    Thread.Sleep(2000);
    return "耗时两秒的任务处理完成,中间界面不出现卡死。";
    });

    resultLabel.Text = result;
    }

使用 Thread

直接使用Thread类来创建后台线程,需要注意线程间的通信问题,尤其是UI线程的更新需要使用Invoke方法。

  • 新建按钮改名为 threadButton
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    private void threadButton_Click(object sender, EventArgs e)
    {
    resultLabel.Text = "耗时任务开始!";

    Thread thread = new Thread(() =>
    {
    Thread.Sleep(2000);
    this.Invoke(new Action(() =>
    {
    resultLabel.Text = "耗时两秒的任务处理完成,中间界面不出现卡死。";
    }));
    });
    thread.Start();
    }

使用 Task 并结合 Progress<T>

  • 新建 ProgressBar 进度条控件改名为 progressBar
  • 新建按钮改名为 progressButton
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    private async void progressButton_Click(object sender, EventArgs e)
    {
    var progress = new Progress<int>(value =>
    {
    // 在UI线程上更新进度
    progressBar.Value = value;
    });

    await Task.Run(() =>
    {
    for (int i = 0; i <= 100; i++)
    {
    // 这里不直接调用 progressBar 是因为Task内已经是在另一个线程上了
    // progressBar 控件是在UI线程创建的,其他线程不能直接访问
    // progressBar.Value = i;
    (progress as IProgress<int>).Report(i);
    Thread.Sleep(50); // 模拟耗时操作
    }
    });
    }