从BackgroundWorker线程访问UI控件

时间:2010-12-13 12:30:28

标签: c# winforms asynchronous udp

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

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

alt text

称之为:

alt text

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

现在,当我运行应用程序并按下刷新按钮时,会出现以下异常:

alt text

我如何解决这个问题?

编辑:

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

listBoxServers.Items.Clear();

6 个答案:

答案 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
}