Winform:多线程同时更新UI

发布于 2024-10-09 19:05:31 字数 1311 浏览 6 评论 0原文

我继承了一些具有两个非 UI 线程的代码,用于更新各种 WinForm 控件。
该代码使用 InvokeRequired 和 Invoke 来更新 UI;但是,我仍然偶尔会收到错误:跨线程操作无效:控制“lvReports”在创建的线程以外的线程上访问。

我怀疑我正在处理竞争条件,并且需要在下面的方法中引入锁,但是也就是说,我可以找到数十个有关如何从非 UI 线程安全更新 UI 的示例,但没有关于的示例或讨论如何处理在竞赛场景中更新相同控件的两个线程。

所以我的问题是:在给定竞争条件并且我需要从非 UI 线程更新 UI 的情况下,如何重写下面的代码来正确处理更新 UI?

// two separate theads call this method in a instance of a WinForm
private void LoadReports()
{
  if (this.InvokeRequired)
  {
    this.Invoke(new MethodInvoker(this.LoadReports));
  }
  else
  {
    // some code removed to keep exampe simple...
    SetCtlVisible(lvReports, true);

    if (this.InvokeRequired)
    {
      this.Invoke((MethodInvoker)delegate { lvReports.Refresh(); });
    }
    else
    {
       lvReports.Refresh();
    }
  }
}

delegate void SetVisibleCallback(Control ctl, bool visible);
private void SetCtlVisible(Control ctl, bool visible)
{
  if (ctl.InvokeRequired)
  {
    SetVisibleCallback d = new SetVisibleCallback(SetCtlVisible);
    ctl.Invoke(d, new object[] { ctl, visible });
  }
  else
  {
    ctl.Visible = visible;
  }
}

以下是一些想法: this.InvokeRequired 在任何时候都与 ctl.InvokeRequired 不同吗? 考虑到第一个测试,是否需要第二个 InvokeRequired 测试? 如果我保留第一个InvokeRequired,是否需要实现SetCtlVisible? 我应该删除第一个 InvokeRequired 并将所有代码保留在 else 子句中吗? else 子句周围是否需要锁?

I inherited some code that has two non-UI threads that update various WinForm controls.
The code is using InvokeRequired and Invoke to update the UI; however, I still once in a while get the error: Cross-thread operation not valid: Control 'lvReports' accessed on a thread other than it was created on.

I suspect I am dealing with a race condition and that I need to introduce a lock into the method below, but that said, I can find dozens of examples on how to update UI from a non-UI thread safely but no examples or discussion on how to deal with two threads updating the same controls in a race scenario.

So my question is: how do I rewrite the code below to handle updating the UI properly given a race condition and that I need to update UI from non-UI threads?

// two separate theads call this method in a instance of a WinForm
private void LoadReports()
{
  if (this.InvokeRequired)
  {
    this.Invoke(new MethodInvoker(this.LoadReports));
  }
  else
  {
    // some code removed to keep exampe simple...
    SetCtlVisible(lvReports, true);

    if (this.InvokeRequired)
    {
      this.Invoke((MethodInvoker)delegate { lvReports.Refresh(); });
    }
    else
    {
       lvReports.Refresh();
    }
  }
}

delegate void SetVisibleCallback(Control ctl, bool visible);
private void SetCtlVisible(Control ctl, bool visible)
{
  if (ctl.InvokeRequired)
  {
    SetVisibleCallback d = new SetVisibleCallback(SetCtlVisible);
    ctl.Invoke(d, new object[] { ctl, visible });
  }
  else
  {
    ctl.Visible = visible;
  }
}

Here are some thoughts:
Does this.InvokeRequired differ from ctl.InvokeRequired at any time?
Is the second InvokeRequired test needed given the first?
Is the implementation of SetCtlVisible needed if I keep the first InvokeRequired?
Should I delete the first InvokeRequired and keep all the code in the else clause?
Is lock needed around the else clause?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

娇女薄笑 2024-10-16 19:05:31

像这样使用 InvokeRequired 是一种反模式。您知道该方法是从线程调用的,InvokeRequired 应该始终为 true。

现在您可以使用它来解决您的问题。如果它是假的,那么就有严重的问题了。抛出异常,调试器将停止并让您找出它无法正常工作的原因。并且始终调用 Invoke(),调用一个小辅助方法来完成 LoadReports() 的其余部分。

另请注意,您在其余代码中使用了错误的方法。您知道 LoadReports() 的其余部分在 UI 线程上运行,您使用了 Invoke()。没有必要再次测试它,包括在 SetCtlVisible() 内部。

获得炸弹的典型原因是线程在创建窗体窗口之前过早运行 LoadReports()。你需要将其联锁起来。窗体的 Load 事件是信号。

Using InvokeRequired like this is an anti-pattern. You know that this method is getting called from a thread, InvokeRequired should always be true.

Now you can use it to troubleshoot your problem. If it is false then there's something seriously wrong. Throw an exception, the debugger will stop and let you find out why it isn't working properly. And always call Invoke(), invoke to a little helper method that does the rest of LoadReports().

Also note that you are using it wrong in the rest of your code. You know that the remainder of LoadReports() runs on the UI thread, you used Invoke(). No point in testing it again, including inside SetCtlVisible().

The typical reason for getting the bomb is because the thread is running LoadReports() too soon, before the form's window is created. You need to interlock that. The form's Load event is the signal.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文