委托和跨线程异常

3
每当我使用委托更新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; }));
     }
 }

请发布完整的异常信息。 - John Saunders
clsConnect 调用 displayResponse 吗? - Ikaso
4个回答

5
DataReceived事件始终在线程池线程上引发。您无法更新任何UI控件,必须使用Control.BeginInvoke()。测试InvokeRequired没有意义,它总是为true。
以下是需要记住的几点:
- 不要为每个接收到的字符或字节调用Control.BeginInvoke。这将使UI线程崩溃。缓冲串行端口获取的数据,直到获得完整的响应。使用SerialPort.ReadLine()通常很有效,因为许多设备发送以换行符(SerialPort.NewLine)终止的字符串。 - 关闭程序可能很困难。您必须确保在串行端口停止发送之前保持表单处于活动状态。在关闭表单后收到事件会生成ObjectDisposed异常。使用FormClosing事件关闭串行端口并启动一个一秒定时器。只有当计时器过期时才真正关闭表单。 - 避免使用Control.Invoke而不是BeginInvoke。在调用SerialPort.Close()时,它可能会导致程序死锁。
有很多方法可以遇到麻烦。考虑使用自己的线程而不是DataReceived来避免它们。

2

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

每个委托调用都会启动一个新线程吗?

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

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

考虑这一点,并阅读可能为您解释问题的文章

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 是正确的。 我在编写该代码时忽视了这一点。 - Sky Sanders
错误的方式是,在Close()调用上,Invoke()很常见地导致死锁。 - Hans Passant
@Hans,您显然有更多处理这个问题的经验,我将撤回我的修改,但为什么不添加一个代码示例来检查您的答案呢?这样我会更加放心。 - Sky Sanders

0

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


如果我理解正确的话,Port_DataReceived 最终会调用 displayResponse。如果是这样的话,那么 Port_DataReceived 是从另一个线程中调用的。IO API 是否会调用此方法? - Ikaso

0

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


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