运行 没有阻塞主线程的代码

Run code without block main thread

我需要生成 n 个随机字符串,这个过程可能需要一段时间并阻塞主线程 UI。为了避免这种情况并让用户在进程为 运行 时使用该程序,我决定使用 backGroundWorker. But it didn't work well and the main thread still is blocked. In my DoWork 事件我有这样的事情:

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
           // using Invoke because I change value of some controls, e.g,   ListView, labels and a progressbar
            this.Invoke((Action)delegate
            {
                for (int i = 0; i < nSteps; ++i)
                {
                    string s = getnStr();
                    // ...
                    int progress = (int)(100.0 / nSteps * (i + 1));
                    backgroundWorker1.ReportProgress(progress);
                }
            });
        }

尽管我在循环中调用 ReportProgress(),但进度条仅在循环完成时更改其值。

为什么会发生这种情况,我该如何解决?

Invoke... 这就是你弄错的地方。您只是创建一个后台工作程序,以便将代码调用回主线程(这就是 Invoke 所做的)。

实际上,您应该要么只对控件访问使用调用,要么只在 ReportProgress 处理程序中修改控件,然后确保每次需要时都调用 ReportProgress 方法。

编辑:澄清:调用的问题是您正在为后台工作人员应该执行的整个工作负载调用 UI 线程。

另一个答案解释了您所看到的行为。请先阅读。以下是解决此问题的方法:

BackgroundWorker 已过时,因为我们现在有 Taskawait。它自动化了很多。您的代码可能应该如下所示:

            //In the button click handler:
            for (int i = 0; i < nSteps; ++i)
            {
                await DoSomeWorkAsync();
                int progress = (int)(100.0 / nSteps * (i + 1));
                SetProgressBarValue(progress);
            }

真的就这些了。您需要确保 DoSomeWorkAsync 不会阻塞。它必须 return 一个 Task.

除了报告进度之外,ReportProgress() 方法还可以用作通用的异步事件调度程序(例如更新 UI 控件中的文本):

 for (int i = 0; i < nSteps; ++i)
 {
   string s = getnStr();

   // Update text
   backgroundWorker1.ReportProgress(0, "My text..");    

   // Update progress
   int progress = (int)(100.0 / nSteps * (i + 1));
   backgroundWorker1.ReportProgress(progress);
 }

ProgressChanged 事件处理程序看起来像这样:

void backgroundWorker1_ProgressChanged(object sender,  ProgressChangedEventArgs e)
{
     if (e.UserState != null)
     {
           // Handle text update
           label1.Text = (string)e.UserState;
           return;
     } 

     progressBar1.Value = e.ProgressPercentage;   
}