我在Windows窗体上有一个调用RunWorkerAsync()方法的按钮,然后执行一个操作,然后更新同一窗体上的ListBox。
DoWork事件结束后,我为事件分配了结果(这是一个列表),我处理RunWorkerCompleted()事件,然后执行以下代码来更新我的列表框
称之为:
(道歉,代码格式不起作用)
现在,当我运行应用程序并按下刷新按钮时,会出现以下异常:
我如何解决这个问题?
编辑:
在下面的语句中抛出异常,这发生在DoWork方法中,我清除内容以使列表保持最新;
listBoxServers.Items.Clear();
答案 0 :(得分:13)
您不能在列表框中调用Invoke
,而是在表单上调用...
this.Invoke((MethodInvoker)delegate()
{
// Do stuff on ANY control on the form.
});
...
。对于WinForms应用程序,我使用类似:
MethodInvoker
根据.NET版本的不同,您可能必须自己声明public delegate void MethodInvoker();
的委托
ReportProgress
但是,您也可以考虑使用Background Worker的{{1}}功能。应该在表单的线程的上下文中调用相应的事件处理程序。
答案 1 :(得分:11)
这是一个我觉得非常方便的片段:
public static void ThreadSafe(Action action)
{
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Normal,
new MethodInvoker(action));
}
您可以将Action
类型的任何委托传递给它,或者只传递一个像这样的lambda:
ThreadSafe(() =>
{
[your code here]
});
或
ThreadSafe(listBoxServers.Items.Clear);
答案 2 :(得分:9)
每次你需要跨线程运行时,我所做的就是这样:
listBoxServers.BeginInvoke(
(Action)
(() => listBoxServers.Items.Clear()));
答案 3 :(得分:1)
不允许后台线程更新Windows应用程序中的UI,因此您必须将控件还原回UI线程以进行实际更新。
创建一个将在主线程上调用UpdateServerDetails的方法,如下所示:
private void DispatchServerDetails(List<ServerDetails> details)
{
Action<List<ServerDetails>> action = UpdateServerDetails;
Dispatcher.Invoke(action)
}
然后拨打DispatchServerDetails
而不是UpdateServerDetails
。
一些警告:
- 这在WPF应用程序中效果最好,对于WinForms,你需要跳过一些箍,或者你可以使用InvokeRequired
- UI更新仍然是同步的,因此如果UpdateServerDetails做了很多工作,它将阻止UI线程(不是你的情况,只是为了安全起见)。
答案 4 :(得分:1)
在Windows窗体项目中使用Invoke可能有点棘手,有一些记录但很容易被遗漏的陷阱。我推荐使用你会在这个问题中找到的东西:
Is it appropriate to extend Control to provide consistently safe Invoke/BeginInvoke functionality?
它处理不需要调用,从不同线程调用,是否创建句柄的情况,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();
}
}
答案 5 :(得分:0)
我刚刚想出了一种不使用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
}