[英]Getting “UnauthorizedAccessException” in a BackgroundWorker without accessing UI thread
[英]Accessing UI Control from BackgroundWorker Thread
我在Windows窗体上有一个调用RunWorkerAsync()方法的按钮,然后执行一个操作,然后更新同一窗体上的ListBox。
DoWork事件完成后,我为事件分配结果(这是一个列表),我处理RunWorkerCompleted()事件,然后执行以下代码来更新我的列表框
这称之为:
(道歉,代码格式化不起作用)
现在,当我运行应用程序并按下刷新按钮时,会出现以下异常:
我怎么能绕过这个?
编辑:
在下面的语句中抛出异常,这发生在DoWork方法中,我清除内容以使列表保持最新;
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的ReportProgress
功能。 应该在表单的线程的上下文中调用相应的事件处理程序。
这是一个我觉得非常方便的片段:
public static void ThreadSafe(Action action)
{
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Normal,
new MethodInvoker(action));
}
您可以将任何Action
类型的委托传递给它,或者简单地传递一个像这样的lambda:
ThreadSafe(() =>
{
[your code here]
});
要么
ThreadSafe(listBoxServers.Items.Clear);
每次你需要跨线程运行时,我所做的就是这样的事情:
listBoxServers.BeginInvoke(
(Action)
(() => listBoxServers.Items.Clear()));
不允许后台线程更新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线程(不是你的情况,只是为了安全起见)。
在Windows窗体项目中使用Invoke可能有点棘手,有一些记录但很容易被遗漏的陷阱。 我推荐使用像你在这个问题中找到的东西:
是否适合扩展Control以提供始终如一的安全Invoke / BeginInvoke功能?
它处理不需要调用,从不同线程调用,是否创建句柄的情况,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
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.