从非UI线程更新控件

3
先生们,我知道这个问题已经被问了很多次,但我仍然找不到一条合理的答复。
我的表单上有超过400个控件。
我有一个后台线程轮询一堆设备并收集各种数据以在表单上显示。
然后我调用一个方法“UpdateDisplay(string [] data)”。 这个程序将在字符串数组data[]中获取所有信息,并填充在表单上的所有组件。 我有标签、文本框被填充。 面板、表格布局和其他控件被显示和隐藏。
数百个组件!!
如果我必须测试每个组件来查看是否需要调用我的程序,那么我的程序将变成50亿行代码!!!
有没有一些简单的方法可以只查看是否需要在UI线程上调用整个UpdateDisplay方法,而不是它所涉及的400多个控件?
我加入了以下代码:
    if (InvokeRequired)
    {
        BeginInvoke(new MethodInvoker(() => UpdateDisplay(data)));
    }

作为display方法中的第一个语句,我不再收到有关从非UI线程调用UI组件的运行时异常。
接下来是update方法的其余部分,其中数百个组件使用data[]中的信息进行更新。
但现在我遇到了一堆System.Forms.dll中的System.InvalidOperationException???
如果我将调试异常选项设置为在所有invalidoperationexceptions上中断,我会看到它们在UpdateDisplay方法中随机抛出,提示从非UI线程更新组件的错误信息。
请问有人能帮我理解并解决这个问题吗?
我可以发布整个UpdateDisplay方法以展示每个组件更新调用都必须包含一个invokerequired if语句是多么荒谬。 不夸张地说,这将每个控件增加三行代码,或者大约1200行额外的代码! 简直太疯狂了!

“更新方法的其余部分” - 这是一个简单的错误吗?在BeginInvoke之后缺少了return吗? BeginInvoke将委托安排在UI线程上执行,但如果您确实在if之后有更多逻辑,则仍将在工作线程上运行。 - Dark Falcon
请查看https://dev59.com/wnRB5IYBdhLWcg3wUVtd#661706 - Rod Lima
异常消息和堆栈跟踪是什么? - Yacoub Massad
你为什么要测试 InvokeRequired?你确定你正在后台线程上运行 UpdateDisplay 吗? - Yacoub Massad
3个回答

2
这是一个示例,展示我如何从另一个线程更新ListBox中的消息。
private void WriteProgressMessage(string message)
{
    if (this.InvokeRequired)
    {
        this.Invoke(new Action(() => this.WriteProgressMessage(message)));
    }
    else
    {
        this.ProgressList.Items.Add(message);
        this.ProgressList.SelectedIndex = this.ProgressList.Items.Count - 1;
        this.ProgressList.SelectedIndex = -1;
    }
}

这和我做的不一样吗?我只是没有将 if (InvokeRequired) 后面的所有剩余代码包装在 else 块中,因为似乎不需要。 - ctbram
如果不需要,你就不会收到错误信息。既然你收到了错误信息,那么很明显是需要的。 - Dark Falcon

2

您没有提供足够的代码,但如果您的方法看起来像这样:

void UpdateDisplay(string[] data)
{
    if (InvokeRequired)
    {
        BeginInvoke(new MethodInvoker(() => UpdateDisplay(data)));
    }
    Label1.Text = data[0];
    // Update more controls here
}

如果您两次运行UpdateDisplay,一次是由于BeginInvoke在UI线程上运行,另一次是在工作线程上,当BeginInvoke返回时。通常的模式是,如果您使用InvokeBeginInvoke来调用自己,则该方法立即返回并且不进行进一步处理。
void UpdateDisplay(string[] data)
{
    if (InvokeRequired)
    {
        BeginInvoke(new MethodInvoker(() => UpdateDisplay(data)));
        return; // Don't run any code below when BeginInvoke returns
    }
    Label1.Text = data[0];
    // Update more controls here
}

JimmyV也提供了另一种版本,在BeginInvoke之后不再执行任何其他工作:

void UpdateDisplay(string[] data)
{
    if (InvokeRequired)
    {
        BeginInvoke(new MethodInvoker(() => UpdateDisplay(data)));
    }
    else // Don't run any code below when BeginInvoke returns
    {
        Label1.Text = data[0];
        // Update more controls here
    }
}

太好了!看起来就是这样,我想我终于理解了这个调用的东西。我的问题之一是我还没有完全理解lambda表达式。但是在我的版本中添加了return;,就像第一个示例一样,错误已经停止了。我也注意到Dark Falcon也找到了正确的问题。非常感谢你们所有人。随着C#和.NET API不断变异,跟上步伐变得越来越困难。 - ctbram

0
一个可能的原因是,由于您正在使用BeingInvoke而不是Invoke来更新UI线程,可能存在同步问题。没有看到更完整的代码示例很难确定,但尝试调用Invoke而不是BeingInvoke,看看是否会减少错误。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接