繁体   English   中英

从BackgroundWorker线程访问UI控件

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM