在另一个线程内操作UI元素

我试图在WinForms C#应用程序中创建一个单独的线程,启动一个控制ProgressBar(marquee)的后台工作程序。 问题在于,当我尝试将条形图设置为可见时,它什么都不做,我尝试过多种forms的Invoke,但它们似乎没有帮助。

从单独的线程调用以下方法progressBarCycle

  BackgroundWorker backgroundWorker = new BackgroundWorker(); public void progressBarCycle(int duration) { backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork); backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged); backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted); backgroundWorker.WorkerReportsProgress = true; backgroundWorker.WorkerSupportsCancellation = true; backgroundWorker.RunWorkerAsync(duration); } private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) { BackgroundWorker worker = sender as BackgroundWorker; worker.ReportProgress(0); DateTime end = DateTime.Now.AddMilliseconds((int)e.Argument); while (DateTime.Now  progressBar1.Visible = false)); // else progressBar1.Visible = false; // } // else // if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = false)); // else progressBar1.Visible = false; } private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e) { if (!this.IsHandleCreated) this.CreateHandle(); statusStrip1.Invoke((MethodInvoker)delegate { progressBar1.Visible = true; }); // if (!this.IsHandleCreated) // { // this.CreateHandle(); // if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = true)); // else progressBar1.Visible = true; // } // else // if (InvokeRequired) this.Invoke((MethodInvoker)(() => progressBar1.Visible = true)); // else progressBar1.Visible = true; } 

我错过了一些明显的东西吗? 评论部分是我尝试过的其他内容。

在UI线程上已经引发了ProgressChanged (通过sync-context); 你的ProgressChanged不需要做那个Invoke – 它可以直接操作UI(相比之下, DoWork绝对不能这样做)。 也许真正的问题是你没有在循环中做任何worker.ReportProgress(...) – 所以它只在开始时发生一次。

这是一个完整的例子:

 using System; using System.ComponentModel; using System.Threading; using System.Windows.Forms; static class Program { [STAThread] static void Main() { Application.EnableVisualStyles(); using (var worker = new BackgroundWorker { WorkerReportsProgress = true }) using (var progBar = new ProgressBar { Visible = false, Step = 1, Maximum = 100, Dock = DockStyle.Bottom }) using (var btn = new Button { Dock = DockStyle.Top, Text = "Start" }) using (var form = new Form { Controls = { btn, progBar } }) { worker.ProgressChanged += (s,a) => { progBar.Visible = true; progBar.Value = a.ProgressPercentage; }; worker.RunWorkerCompleted += delegate { progBar.Visible = false; }; worker.DoWork += delegate { for (int i = 0; i < 100; i++) { worker.ReportProgress(i); Thread.Sleep(100); } }; btn.Click += delegate { worker.RunWorkerAsync(); }; Application.Run(form); } } } 
  1. 从UI线程运行progressBarCycleRunWorkerAsync将为您创建新线程。
  2. backgroundWorker_ProgressChanged只需调用progressBar1.Visible = true; 。 不需要Invoke
  3. 最好还添加progressBar1.Refresh();

另一个需要注意的可能是进度条正在您的UI线程上运行。 为了显示进度条并重绘自身以显示新的进度数量,UI线程必须自由运行,在主应用程序循环中处理Windows消息。

因此,如果您启动后台工作线程,但是您的UI线程处于忙等待循环中等待它完成,或者关闭并执行大量其他工作,那么它将不会处理Windows消息,并且您的进度条将是“反应迟钝”。 您需要释放UI线程,以便仍然发生此更新(即从事件处理程序返回并允许UI继续正常运行)。

这样做的危险在于,如果UI处于活动状态,则用户仍然可以与其进行交互。 因此,您必须编写要在后台工作程序处于活动状态时要注意的UI,并正确处理该情况(问题可能包括:允许用户在其已经运行时再次启动后台工作程序,UI尝试在工作时显示信息线程忙于更新它,用户决定加载新文档或在后台工作人员忙时退出,等等。 这两个主要的解决方案是将每个UI包裹在一个保护盾中,以防止在后台工作运行时发生任何危险的事情(如果你有很多控件以这种方式包装,这可能会很多工作,并且很容易犯错误,让错误漏掉)或让UI“不受保护”但添加一个IMessageFilter,通过抑制他们传入的Windows消息(WM_KEYPRESS等)来阻止所有“危险”的用户交互(点击和按键)后台处理是活动的。