STA线程模型中操纵串口的注意点

SerialPort的DataReceived事件会在一个或多个字节的数据可用时被触发,具体触发的精确时间由OS和驱动来决定,同时接收到数据的时间与在.NET时事件的触发时间会有一个短暂的延迟。


在DataReceived事件中要尽量减少对单线程模型对象的操作,如[STA]的WinForm、Console,因为串口监听线程和UI线程极易造成并发冲突而死锁:

如:(项目代码摘录)

private void ReceivedData(string line)
        {
            m_ReceivedTextList.Items.Insert(0,
                string.Format(
                "[{0}]\t{1} ===> {2}",
                DateTime.Now.ToShortTimeString(),
                m_commandComboBox.Text.Trim(),
                line
                )
            );
        }

        void OnSerialPort_DataReceived(object sender, StringEventArgs e)
        {
            if (this.InvokeRequired)
            {//这里不能用Invoke
                this.BeginInvoke((MethodInvoker)delegate
                {
                    ReceivedData(e.Message);
                });
            }
            else
            {
                ReceivedData(e.Message);
            }
            m_lastReceivedLine = e.Message;
            m_autoResetEvent.Set();
        }

private bool IdentifyDevice(string id, string portName)
        {
            if (!m_serialPortHelper.IsOpened)
            {
                Parameter.PortName = portName;
                m_serialPortHelper.SetParameter(Parameter);
                m_serialPortHelper.OpenPort();
            }
            m_serialPortHelper.Send("*IDN?");
            var timeout = ! m_autoResetEvent.WaitOne(1000);
            //ClosePort();
            if (timeout)
            {
                return false;
            }
            else
            {
                return m_lastReceivedLine.StartsWith(id);
            }
        }

private void OnAutoFindPortButton_Click(object sender, EventArgs e)
        {
            var oldPort = Parameter.PortName;
            bool foundPort = false;
            this.Enabled = false;
            if (!string.IsNullOrEmpty(DeviceID))
            {
                if (!IdentifyDevice(DeviceID, Parameter.PortName))
                {
                    //ClosePort();
                    int i = m_allPortsComboBox.Items.Count - 1;
                    for (; i >= 0 ; i--)
                    {
                        if (m_allPortsComboBox.Items[i].ToString() != oldPort)
                        {
                            m_allPortsComboBox.SelectedIndex = i;
                            if (IdentifyDevice(DeviceID, m_allPortsComboBox.Text))
                            {
                                break;
                            }
                            //ClosePort();
                        }
                    }
                    foundPort = i >= 0;
                }
                else
                {
                    foundPort = true;
                }
                if (foundPort)
                {
                    MessageBox.Show(string.Format("Find port with specified device ID: {0}", DeviceID));
                }
                else
                {
                    MessageBox.Show(string.Format("Can not find port with specified device ID: {0}", DeviceID));
                    m_allPortsComboBox.SelectedItem = oldPort;
                }
                this.Enabled = true;                
            }
            else
            {
                MessageBox.Show("Can not find port without device ID.");
            }
        }

其中IdentifyDevice是在UI主线程中调用。在STA这样的编程模型几乎不会有任何实际的效果。因为Send之后WaitOne,造成UI主线程阻塞等待OnSerialPort_DataReceived中去释放Event。而由于OnSerialPort_DataReceived中要对UI主线程进行操作,所以会阻塞来等待UI主线程释放。这样只有WaitOne的超时之后才会解除UI主线程的阻塞,但这时timeout已经为true了。这时OnSerialPort_DataReceived中会向ListBox中插入一行信息。如果将WaitOne下的ClosePort注释去掉,则连ListBox中都不会插入一行信息,因为打开串口的时候,SerialPort会创建一个监听线程ListenThread,在这个线程中,等待注册的串口中断,当收到中断后,会调用DataReceived事件。调用完成后,继续进入循环等待,直到串口被关闭退出线程。
ClosePort直接导致关闭DataReceived线程,从而没机会操纵UI了。
解决方法就是在OnSerialPort_DataReceived中用BeginInvoke来操纵UI,因为它不会阻塞当前线程。


另外,在大量发送命令给串口设备时可能会使DataReceived无法触发,这时需要在每个命令后加一个停顿。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值