【问题标题】:Delegates And Cross Thread Exception委托和跨线程异常
【发布时间】:2023-09-08 20:14:02
【问题描述】:

每当我使用委托在 Windows 窗体中更新 UI 时,都会出现跨线程异常 为什么会这样? 是否为每个委托调用启动了新线程?

void Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
       //this call delegate to display data
       clsConnect(statusMsg);
}




 protected void displayResponse(string resp)
 {
     //here cross thread exception occur if directly set to lblMsgResp.Text="Test";
     if (lblMsgResp.InvokeRequired)
     {
        lblMsgResp.Invoke(new MethodInvoker(delegate { lblMsgResp.Text = resp; }));
     }
 }

【问题讨论】:

  • 请发布完整的例外情况。
  • clsConnect 调用 displayResponse?

标签: c# delegates multithreading


【解决方案1】:

DataReceived 事件总是在线程池线程上引发。您无法更新任何 UI 控件,您必须使用 Control.BeginInvoke()。测试 InvokeRequired 没有意义,它总是正确的。

这里有几件事要记住:

  • 不要为收到的每个字符或字节调用 Control.BeginInvoke。这将使 UI 线程屈服。缓冲您从串口获得的数据,直到您得到完整的响应。使用 SerialPort.ReadLine() 通常效果很好,许多设备发送的字符串以换行符 (SerialPort.NewLine) 终止。
  • 关闭程序可能很困难。您必须确保表单保持活动状态,直到串行端口停止发送。窗体关闭后获取事件会产生 ObjectDisposed 异常。使用 FormClosing 事件关闭串行端口并启动一秒计时器。只有在计时器到期时才真正关闭表单。
  • 避免使用 Control.Invoke 而不是 BeginInvoke。当您调用 SerialPort.Close() 时,它可能会使您的程序死锁。

惹麻烦的方法很多。考虑使用您自己的线程而不是使用 DataReceived 来避免它们。

【讨论】:

    【解决方案2】:

    Port_DataReceived 显然是一个异步事件处理程序,由端口监控组件上的线程引发。

    是否为每个线程启动了新线程 委托调用?

    不,可能不会。您的端口监控组件正在后台线程上运行轮询,并且每次都从该线程引发事件。

    关键是它是在 UI 以外的线程上调用的,因此您需要使用 Control.Invoke 以及与之关联的模式。

    考虑一下,(阅读post 可能会为您提供启示)

    void Port_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
       //this call delegate to display data
       UpdateTheUI(statusMsg);
    }
    
    private void UpdateTheUI(string statusMsg)
    {
        if (lblMsgResp.InvokeRequired)
        {
            lblMsgResp.BeginInvoke(new MethodInvoker(UpdateTheUI,statusMsg));
        }
        else
        {
           clsConnect(statusMsg);
        }
    }
    

    说了这么多,如果我没有指出间接性很麻烦,那我就失职了。

    【讨论】:

    • BeginInvoke 更改为Invoke,Hans 是对的。我在 CP 编码时忽略了这一点。
    • 错误的方法,Invoke() 通常会导致 Close() 调用死锁。
    • @Hans,你显然有更多处理这个问题的经验,我将恢复我的修订,但你为什么不添加一个代码示例,让我们检查你的答案。我会感觉更好。
    【解决方案3】:

    当某些非 UI 线程更改 UI 元素时,会发生跨线程异常。由于 UI 元素只能在 UI 线程中更改,因此会引发此异常。为了帮助您理解为什么会发生这种情况,您必须发布您的代码。

    【讨论】:

    • 如果我理解正确,Port_DataReceived 最终会调用 displayResponse。如果是这样,则从另一个线程调用 Port_DataReceived。 IO API 会调用这个方法吗?
    【解决方案4】:

    当某个非 UI 线程更改 UI 元素时,会发生跨线程异常。要解决此问题,请使用控件本身的 Invoke 方法。作为额外的,您可以在调用 Invoke 方法之前检查控件上的 InvokeRequired 见msdn

    【讨论】: