从BackgroundWorker线程访问UI控件

我的窗体上有一个调用RunWorkerAsync()方法的button,然后执行一个动作,然后在同一个窗体上更新一个ListBox。

在DoWork事件完成后,我为事件(这是一个列表)分配结果,我处理RunWorkerCompleted()事件,然后执行下面的代码来更新我的列表框

替代文字

这称之为:

替代文字

(道歉,代码格式不起作用)

现在,当我运行应用程序并按下刷新button时,出现以下exception:

替代文字

我将如何解决这个问题?

编辑:

在下面的语句中抛出exception,这发生在DoWork方法中,我清除内容以保持列表最新;

listBoxServers.Items.Clear();

这里有一个我觉得非常好用的片段:

public static void ThreadSafe(Action action) { Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Normal, new MethodInvoker(action)); } 

你可以把它传递给Actiontypes的任何委托或简单地像这样的lambda:

 ThreadSafe(() => { [your code here] }); 

要么

 ThreadSafe(listBoxServers.Items.Clear); 

您不能在列表框上调用Invoke ,而是在窗体上Invoke 。 对于WinForms应用程序,我使用类似于:

 ... this.Invoke((MethodInvoker)delegate() { // Do stuff on ANY control on the form. }); ... 

根据.NET版本,您可能必须自己声明一个MethodInvoker的委托

 public delegate void MethodInvoker(); 

不过,您也可以考虑使用Background Worker的ReportProgressfunction。 应该在表单线程的上下文中调用相应的事件处理程序。

每当你需要在线程中运行一些东西时,我所做的就是这样的:

 listBoxServers.BeginInvoke( (Action) (() => listBoxServers.Items.Clear())); 

后台线程不允许更新Windows应用程序中的用户界面,因此您必须将控制权还原回UI线程才能进行实际更新。

创build一个将在主线程上调用UpdateServerDetails的方法,如下所示:

 private void DispatchServerDetails(List<ServerDetails> details) { Action<List<ServerDetails>> action = UpdateServerDetails; Dispatcher.Invoke(action) } 

然后调用DispatchServerDetails而不是UpdateServerDetails

一些注意事项:
– 这在WPF应用程序中效果最佳,对于WinForms,您需要跳过一些圈,或者您可以使用InvokeRequired
UI更新仍然是同步的,所以如果UpdateServerDetails做了很多工作,它会阻塞UI线程(不是你的情况,只是为了安全起见)。

在Windows窗体项目中使用调用可能有点棘手,有一些陷阱,有logging,但容易错过。 我build议你在这个问题中使用类似的东西:

是否适当扩展控制提供一贯安全的调用/ BeginInvokefunction?

它处理不需要调用的情况,从不同的线程调用,处理是或不被创build,etcetcetc。 如果你不是bool参数的粉丝,可以很容易地修改为SafeInvoke()SafeBeginInvoke()

(这里包括为了您的方便:

 /// Usage: this.lblTimeDisplay.SafeInvoke(() => this.lblTimeDisplay.Text = this.task.Duration.ToString(), false); // or string taskName = string.Empty; this.txtTaskName.SafeInvoke(() => taskName = this.txtTaskName.Text, true); /// <summary> /// Execute a method on the control's owning thread. /// </summary> /// <param name="uiElement">The control that is being updated.</param> /// <param name="updater">The method that updates uiElement.</param> /// <param name="forceSynchronous">True to force synchronous execution of /// updater. False to allow asynchronous execution if the call is marshalled /// from a non-GUI thread. If the method is called on the GUI thread, /// execution is always synchronous.</param> public static void SafeInvoke(this Control uiElement, Action updater, bool forceSynchronous) { if (uiElement == null) { throw new ArgumentNullException("uiElement"); } if (uiElement.InvokeRequired) { if (forceSynchronous) { uiElement.Invoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); }); } else { uiElement.BeginInvoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); }); } } else { if (!uiElement.IsHandleCreated) { // Do nothing if the handle isn't created already. The user's responsible // for ensuring that the handle they give us exists. return; } if (uiElement.IsDisposed) { throw new ObjectDisposedException("Control is already disposed."); } updater(); } } 

我只是想出了一个更简单的方法,而不使用Invoke:

 int fakepercentage = -1; //some loop here......if no loop exists, just change the value to something else if (fakepercentage == -1) { fakepercentage = -2; } else { fakepercentage = -1; } backgroundworker1.ReportProgress(fakepercentage); 

然后在backgroundworker1_ProgressChanged(对象发件人,ProgressChangedEventArgs e)中:

 if (e.ProgressPercentage < 0) { //access your ui control safely here }