避免多次调用 Invoke 以从另一个线程更新 GUI

Posted

技术标签:

【中文标题】避免多次调用 Invoke 以从另一个线程更新 GUI【英文标题】:Avoid Multiple calls to Invoke to update GUI from another Thread 【发布时间】:2018-06-22 02:06:10 【问题描述】:

如何让我的代码更精简,只用一个 Invoke 方法?

private void DecodeTHR(byte[] serialInput)

    startVoltage.Invoke((MethodInvoker)(() => startVoltage.Value = serialInput[2]));
    endVoltage.Invoke((MethodInvoker)(() => endVoltage.Value = serialInput[3]));
    mode.Invoke((MethodInvoker)(() => mode.SelectedIndex = serialInput[4]));

    if (serialInput[5] == 255)
        assistLevelTH.Invoke((MethodInvoker)(() => assistLevelTH.SelectedIndex = 0));
    else
        assistLevelTH.Invoke((MethodInvoker)(() => assistLevelTH.SelectedIndex = serialInput[5] + 1));

    if (serialInput[6] == 255)
        speedLimitTH.Invoke((MethodInvoker)(() => speedLimitTH.SelectedIndex = 0));
    else
        speedLimitTH.Invoke((MethodInvoker)(() => speedLimitTH.SelectedIndex = serialInput[6] - 14));

    startCurrentTH.Invoke((MethodInvoker)(() => startCurrentTH.Value = serialInput[7]));

    // RestartPort();

    if (next_op == rdSingle)
        MessageBox.Show("Throttle Handle flash read successful", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
    else
    
        MessageBox.Show("Flash read successful", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
        next_op = rdIgnore;
    

到目前为止,我找不到任何其他方法,但我确信有一种方法可以在方法内部进行一次调用,然后可以从主 Form 类更新任意数量的 GUI 组件.

由于我有很多这些功能,有人可以提供一个使用我的代码的示例吗?

【问题讨论】:

不要调用。将值写入一些共享变量。让 GUI 通过计时器轮询 Udpates 的这些变量。对这些变量的所有调用应用一些锁定,当然 (docs.microsoft.com/en-us/dotnet/csharp/language-reference/…) 你为什么还要在后台线程中运行它呢?据我所知,您没有执行任何繁重的计算/阻塞任务。 @VisualVincent 该函数在 SerialPort.DataReceived 事件处理程序中运行。 请参阅下面的答案以获取更简单的调用方式。 【参考方案1】:

由于您没有执行任何繁重的工作,因此只需将所有内容包装在一个调用中即可。最好只调用表单而不是其中一个控件,因为控件无论如何都会查找父表单。

请记得检查InvokeRequired,因为如果没有必要,您不想调用。事实上,通过检查InvokeRequired,您可以让您的方法自行调用:

private void DecodeTHR(byte[] serialInput)

    if(this.InvokeRequired)
    
        this.Invoke((MethodInvoker)(() => DecodeTHR(serialInput)));
        return; //This is to ensure that the method does not execute twice.
    

    startVoltage.Value = serialInput[2];
    endVoltage.Value = serialInput[3];
    mode.SelectedIndex = serialInput[4];

    if (serialInput[5] == 255)
        assistLevelTH.SelectedIndex = 0;
    else
        assistLevelTH.SelectedIndex = serialInput[5] + 1;

    if (serialInput[6] == 255)
        speedLimitTH.SelectedIndex = 0;
    else
        speedLimitTH.SelectedIndex = serialInput[6] - 14;

    startCurrentTH.Value = serialInput[7];

    // RestartPort();

    if (next_op == rdSingle)
    
        MessageBox.Show("Throttle Handle flash read successful", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
    else
    
        MessageBox.Show("Flash read successful", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
        next_op = rdIgnore;
    

【讨论】:

所以这就是最初调用 DecodeTHR() 函数的方法。这样做是否正确:i.imgur.com/6GxIueJ.png?或者我总是必须像递归函数一样从函数内部调用 Invoke?我更新了 DataReceived 和 DecodeTHR 中的 GUI 控件。 DataReceived 也是生成第二个线程的事件。 @UnlimitedSlime :首先,最好让事件本身保持多线程。 IE。不要调用它。 -- 回答你的问题:你不必从内部调用DecodeTHR(),但如果你想像你的形象那样做,那么你必须这样做:pastebin.com/vPBRbveH 尝试将所有 GUI 更新代码放在单个 Invoke() 调用中。最好将它们放在您调用的另一种方法中,就像我在上面共享的链接中一样。另外,请注意 return; 仅应在您调用 内部 本身的方法时使用。 所以,正如我所想,我不得不将我的一些代码放在另一种方法中。我基本上在 DataReceived 事件处理程序中调用了 4-5 个方法,对于这些方法来说一切正常,但是我有一些额外的 GUI 更新,它们不是外部方法的一部分,所以我需要为每个方法调用一次,因为你说我无法调用我的 SerialPort.DataReceived:i.imgur.com/7dzswvi.jpg 我无法避免多次调用“额外代码”部分,我必须将其包装并放入另一个方法中...... 顺便说一句,我不明白你为什么这样做。InvokeRequired:它检查调用者是否必须使用 Invoke 方法来执行对控件的方法调用,但是因为我确定我的所有控件是在与我调用的线程不同的线程中创建的,它永远是真的。

以上是关于避免多次调用 Invoke 以从另一个线程更新 GUI的主要内容,如果未能解决你的问题,请参考以下文章

使用 Invoke 从另一个线程获取 tabControl SelectedTab

C# invoke使用

添加注释以从另一个视图映射

如何传递逗号分隔的输入参数以从另一个存储过程调用存储过程

c#使用这个是安全的吗?Invoke?

Invoke 用法