C# SerialPort串口ReadTimeout 超时异常。“System.TimeoutException”类型的未经处理异常在 System.dll 中发生 其他信息: 操作已超时。

一、简介
系统采用之前的系统:

相关的链接为

https://blog.csdn.net/u011854789/article/details/51895014

https://blog.csdn.net/weixinhum/article/details/53684151

http://www.cnblogs.com/Traveller-Lee/p/6940221.html(主要参考)

(一)WPF工程做上位机与彩屏(或单片机)进行串口通信、解决彩屏(或单片机)只能发送信息不能接受信息问题。

我是在上位机的接收彩屏的信息状态下,收到异常信息:

“System.TimeoutException”类型的未经处理的异常在 System.dll 中发生   其他信息: 操作已超时。
二、超时原因及其解决办法
        超时原因,是因为使用了SerialPort.ReadTimeout 方法和 SerialPortObj.ReadLine()方法。其中,SerialPort.ReadTimeout方法是设定读取的时间间隔。此外, SerialPortObj.ReadLine()表示读取接收缓存区的字节,如未在SerialPort.ReadTimeout设定的时间间隔内收到信息,则抛出异常。

       那么,SerialPort为什么要自带这些方法呢?是多余了吗?显然不是,它是有着其他用途的。我们往后再讨论SerialPort.ReadTimeout 方法和 SerialPort.ReadLine()方法的应用场景。

         现在,我们回过头来解决正常的就接收问题,我只要能够任何时间段、任何连接状态下,只要能够正常接收东西即可。我采用的方法是

    总之,我们在这里采用超时方法ReadLine()。附上代码,该代码已经能够与大彩屏正常收发。

MainWindow.xaml:


    
        
        
        
        
        
    
 
    
        
            
            
            
        
       
        


MainWindow.xaml.cs:

using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;
using System.IO.Ports;
using System.Collections.Generic;
 
namespace PortChat
{
    /// 
    /// MainWindow.xaml 的交互逻辑
    /// 
    public partial class MainWindow
    {
 
        List protocolsList = new List();
 
        #region 包头包尾指令
        //列表
        byte[] bytHomeUserListConstantData              = new byte[] { 0xEE, 0xB1, 0x5B, 0x00, 0x01, 0x00, 0x1B, 0xFF, 0xFC, 0xFF, 0xFF };//11个数
        byte[] bytStudyOptionalProtocolListConstantData = new byte[] { 0xEE, 0xB1, 0x5B, 0x00, 0x02, 0x00, 0x05, 0xFF, 0xFC, 0xFF, 0xFF };//11个数
        byte[] bytStudySelectedBodyListConstantData     = new byte[] { 0xEE, 0xB1, 0x5B, 0x00, 0x02, 0x00, 0x22, 0xFF, 0xFC, 0xFF, 0xFF };//11个数
        //高压
        byte[] bytStudyExposeKvConstantData             = new byte[] { 0xEE, 0xB1, 0x10, 0x00, 0x02, 0x00, 0x2F, 0xFF, 0xFC, 0xFF, 0xFF };//11个数
        //mA
        byte[] bytStudyExposemAConstantData             = new byte[] { 0xEE, 0xB1, 0x10, 0x00, 0x02, 0x00, 0x31, 0xFF, 0xFC, 0xFF, 0xFF };//11个数
        //ms
        byte[] bytStudyExposemsConstantData             = new byte[] { 0xEE, 0xB1, 0x10, 0x00, 0x02, 0x00, 0x35, 0xFF, 0xFC, 0xFF, 0xFF };//11个数
        //mAs
        byte[] bytStudExposemAsConstantData             = new byte[] { 0xEE, 0xB1, 0x10, 0x00, 0x02, 0x00, 0x38, 0xFF, 0xFC, 0xFF, 0xFF };//11个数
        #endregion
 
        //总包
        byte[] bytListDataSend = new byte[0];
        byte[] bytStudyOptionalProtocolListData = new byte[0];
        byte[] bytStudyOptionalBodyListData = new byte[0];
        byte[] bytStudyExposeKvData = new byte[0];
        byte[] bytStudyExposemAData = new byte[0];
        byte[] bytStudyExposemsData = new byte[0];
        byte[] bytStudExposemAsData = new byte[0];
 
        //获取可选体位列表数据数据并发送
        string strProtocolName1 = "协议1";
        string strProtocolNum1 = "名称1";
        string strProtocolName2 = "协议2";
        string strProtocolNum2 = "名称2";
        string strProtocolName3 = "--协faf议2";
        string strProtocolNum3 = "-77名称faf2";
 
        //串口
        private static readonly SerialPort SerialPortObj = new SerialPort();
        string _recievedData = null;
 
        public MainWindow()
        {
            InitializeComponent();
        }
 
        /// 
        /// 串口连接
        /// 
        /// 
        /// 
        private void Connect_Click(object sender, RoutedEventArgs e)
        {
            if ((BtnConnect.Content as string) == "连接")
            {
                //端口 默认COM3
                SerialPortObj.PortName = CbbPortName.Text;
                //波特率 默认9600
                SerialPortObj.BaudRate = int.Parse(CbbBaudRate.Text);
                //奇偶校验 默认None
                SerialPortObj.Parity = (Parity)Enum.Parse(typeof(Parity), CbbPortParity.Text, true);
                //数据位 默认8
                SerialPortObj.DataBits = int.Parse(TbDataBits.Text);
                //停止位 默认1
                SerialPortObj.StopBits = (StopBits)Enum.Parse(typeof(StopBits), CbbStopBits.Text, true);
                //握手方式 默认None
                SerialPortObj.Handshake = (Handshake)Enum.Parse(typeof(Handshake), CbbHandshake.Text, true);
 
                //SerialPortObj.ReceivedBytesThreshold = 1;
                //SerialPortObj.ReadTimeout = 500;//超过ReadTimeout未接收到,则抛出异常。
                //SerialPortObj.WriteTimeout = 500;
 
                if (!SerialPortObj.IsOpen)
                {
                    SerialPortObj.Open();
                }
 
                BtnConnect.Content = "断开";
                BtnConnect.Background = Brushes.Green;
 
                //委托、调用数据接收方法
                SerialPortObj.DataReceived += Recieve;
            }
            else
            {
                try // just in case serial port is not open could also be acheved using if(serial.IsOpen)
                {
                    SerialPortObj.Close();
                    BtnConnect.Content = "连接";
                    BtnConnect.Background = Brushes.White;
                }
                catch (Exception)
                {
                    // ignored
                }
            }
        }
 
        private delegate void UpdateUiTextDelegate(string text);
 
        private void Recieve(object sender, SerialDataReceivedEventArgs e)
        {
            // ReadLine方法要判定 读超时。因此,不用改函数,也不用设置ReadTimeout了。
            //_recievedData = SerialPortObj.ReadLine();
            //开辟接收缓冲区
            byte[] ReDatas = new byte[SerialPortObj.BytesToRead];
            SerialPortObj.Read(ReDatas, 0, ReDatas.Length); //从串口读取数据
            _recievedData = System.Text.Encoding.Default.GetString(ReDatas);
            //实现数据的解码与显示
            //AddData(ReDatas);
 
            //调用委托,将字符信息显示在TextBox控件上。
            Dispatcher.Invoke(DispatcherPriority.Send, new UpdateUiTextDelegate(WriteDate), _recievedData);
        }
 
        private void WriteDate(string text)
        {
            TbReceive.Text += _recievedData;
        }
 
        /// 
        /// 发送指令
        /// 
        /// 
        /// 
        private void SendData_Click(object sender, RoutedEventArgs e)
        {
            //添加列表信息
            protocolsList.Add(new PatientsAndProtocolsList(strProtocolName1, strProtocolNum1));
            protocolsList.Add(new PatientsAndProtocolsList(strProtocolName2, strProtocolNum2));
            protocolsList.Add(new PatientsAndProtocolsList(strProtocolName3, strProtocolNum3));
 
            //病人检查记录发送
            bytListDataSend = GetStringToHex(protocolsList, bytHomeUserListConstantData);
            SerialCmdSend(bytListDataSend);
 
            TbSend.Text = System.Text.Encoding.Default.GetString(bytListDataSend); 
            //可选体位发送
            bytListDataSend = GetStringToHex(protocolsList, bytStudyOptionalProtocolListConstantData);
            SerialCmdSend(bytListDataSend);
 
            //已选体位发送
            bytListDataSend = GetStringToHex(protocolsList, bytStudySelectedBodyListConstantData);
            SerialCmdSend(bytListDataSend);
 
 
        }
 
        /// 
        /// 字符转16进制,16进制保存到字节
        /// 
        /// 
        /// 
        /// 
        private byte[] GetStringToHex( List protocolsList, byte[] bytHeadTailPackageVar)
        {
            //总长度
            //int iTotalPackageLong = 0;
            ////包头、包尾长度
            int iHeadPackageLong = 7;
            int iTailPackageLong = 4;
            ////列表行:高八位
            //int iRowHighSize = 0x00;
            ////列表行:低八位
            //int iRowLowSize = 0x02;
            ////列表行:高八位
            //int iColumnHighSize = 0;//每一行的每列字节数都不固定
            ////列表行:低八位
            //int iColumnLowSize = 0;//每一行的每列字节数都不固定
 
            byte[] bytTotalPackageVar = new byte[500];
            int protocolCount = 0;   
 
            //拷贝包头:7个固定的字节
            Array.ConstrainedCopy(bytHeadTailPackageVar, 0, bytTotalPackageVar, 0, iHeadPackageLong);
 
            #region  确定总行数,及其对应的字节  
            Int16 iRowsNum = 0;//总行数
            iRowsNum = (Int16)protocolsList.Count;
            byte[] byt_iRowsNum = new byte[2];
            byt_iRowsNum = BitConverter.GetBytes(iRowsNum);//一般是两个字节
            Array.ConstrainedCopy(byt_iRowsNum, 1, bytTotalPackageVar, iHeadPackageLong, 1);//byt_iRowsNum的高八位,放到缓存区的低字节区。才符合大彩屏的指令接收要求。
            Array.ConstrainedCopy(byt_iRowsNum, 0, bytTotalPackageVar, (iHeadPackageLong + 1), 1);//byt_iRowsNum的低八位,放到缓存区的高字节区。
            #endregion
 
 
            #region 每行的字符内容,及其对应的字节数目、字节内容
            int iTotalPackageStartIndex = iHeadPackageLong + byt_iRowsNum.Length;
            while (protocolCount < protocolsList.Count)
            {
                string strProtocolNumVar = null;
                string strProtocolNameVar = null;
                string strEachRowContent = null;
                Int16 iEachRowByteNum = 0;//每行的字节数目
                byte[] byt_strEachRowContent = new byte[0];
                byte[] byt_iEachRowByteNum = new byte[0];
 
                #region  确定每行的字符内容,及其对应的字节数目、字节内容
                //字符
                strProtocolNumVar = protocolsList[protocolCount].StrPatientNumOrProtocolNum;
                strProtocolNameVar = protocolsList[protocolCount].StrPatientNameOrProtocolName;
                strEachRowContent = strProtocolNumVar + ";" + strProtocolNameVar + ";";//必须加上分号。
 
                //内容
                byt_strEachRowContent = System.Text.Encoding.Default.GetBytes(strEachRowContent); //每行的字节内容 
                iEachRowByteNum = (Int16)byt_strEachRowContent.Length;
                byt_iEachRowByteNum = BitConverter.GetBytes(iEachRowByteNum);//每行的字节数目,将数目转成字节 
                
                //拷贝
                Array.ConstrainedCopy(byt_iEachRowByteNum, 1, bytTotalPackageVar, iTotalPackageStartIndex, 1);//byt_iEachRowByteNum的高八位,放到缓存区的低字节区。才符合大彩屏的指令接收要求。
                Array.ConstrainedCopy(byt_iEachRowByteNum, 0, bytTotalPackageVar, (iTotalPackageStartIndex + 1), 1);//byt_iEachRowByteNum的低八位,放到缓存区的高字节区。
 
                Array.ConstrainedCopy(byt_strEachRowContent, 0, bytTotalPackageVar, (iTotalPackageStartIndex + byt_iEachRowByteNum.Length), byt_strEachRowContent.Length);
               
                //下标索引
                iTotalPackageStartIndex = iTotalPackageStartIndex + byt_iEachRowByteNum.Length + byt_strEachRowContent.Length;
                #endregion
 
                protocolCount++;
            }
            #endregion 
 
            //拷贝包尾:4个固定的字节
            Array.ConstrainedCopy(bytHeadTailPackageVar, iHeadPackageLong, bytTotalPackageVar, iTotalPackageStartIndex, iTailPackageLong);
            return bytTotalPackageVar;
        }
 
        /// 
        /// 串口发送函数
        /// 
        /// 
        private void SerialCmdSend(byte[] data)
        {
            //若是串口没打开,跳出函数 SerialCmdSend
            if (!SerialPortObj.IsOpen) return;
            try
            {
                //将待发送的内容写到缓冲区
                SerialPortObj.Write(data, 0, data.Length);
            }
            catch (Exception ex)
            {
 
                TbSend.Text = "Failed to SEND" + data + "\n" + ex + "\n";
            }
        }
    }
}


PatientsAndProtocolsList.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace PortChat
{
    //该集合可以装用户集合或列表集合
    class PatientsAndProtocolsList
    {
        public string strPatientNumOrProtocolNum;
        public string strPatientNameOrProtocolName;
        public string strPatientSex;
        public string strPatientAge;
        public string strPatientBody;
        public string strStudyDateTime;
        public string StrPatientNumOrProtocolNum
        {
            get
            {
                return strPatientNumOrProtocolNum;
            }
            set
            {
                strPatientNumOrProtocolNum = value;
            }
        }
 
        public string StrPatientNameOrProtocolName
        {
            get
            {
                return strPatientNameOrProtocolName;
            }
            set
            {
                strPatientNameOrProtocolName = value;
            }
        }
 
        public string StrPatientSex
        {
            get
            {
                return strPatientSex;
            }
            set
            {
                strPatientSex = value;
            }
        }
 
        public string StrPatientAge
        {
            get
            {
                return strPatientAge;
            }
            set
            {
                strPatientAge = value;
            }
        }
 
        public string StrPatientBody
        {
            get
            {
                return strPatientBody;
            }
            set
            {
                strPatientBody = value;
            }
        }
 
        public string StrStudyDateTime
        {
            get
            {
                return strStudyDateTime;
            }
            set
            {
                strStudyDateTime = value;
            }
        }
 
        public PatientsAndProtocolsList() { }
 
        /// 
        /// 保存协议信息
        /// 
        /// 
        /// 
        public PatientsAndProtocolsList(string strProtocolNum, string strProtocolName)
        {
            this.strPatientNumOrProtocolNum = strProtocolNum;
            this.strPatientNameOrProtocolName = strProtocolName;
        }
        /// 
        /// 保存病人信息:5个参数
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        public PatientsAndProtocolsList(string strPatientNum, string strPatientName, string strPatientSex, string strPatientAge, string strPatientBody)
        {
            this.strPatientNumOrProtocolNum = strPatientNum;
            this.strPatientNameOrProtocolName = strPatientName;
            this.strPatientSex = strPatientSex;
            this.strPatientAge = strPatientAge;
            this.strPatientBody = strPatientBody;
        }
        /// 
        /// 保存病人信息:6个参数
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        public PatientsAndProtocolsList(string strPatientNum, string strPatientName, string strPatientSex, string strPatientAge, string strPatientBody, string studyDateTime)
        {
            this.strPatientNumOrProtocolNum = strPatientNum;
            this.strPatientNameOrProtocolName = strPatientName;
            this.strPatientSex = strPatientSex;
            this.strPatientAge = strPatientAge;
            this.strPatientBody = strPatientBody;
            this.strPatientBody = studyDateTime;
        }
    }
}


下面讨论ReadTimeout方法的具体应用场景。

三、SerialPort类ReadTimeout方法的具体应用场景
首先,这些总结或方法是我百度的,我没有实测过。收发往往放到不同的线程中执行。百度了好久,网上也么有说ReadTimeout的应用场景。

但是我从这篇博客了解到,如何读内存的关键信息:https://blog.csdn.net/qq_40741855/article/details/81134682
双方通讯时,一般都需要定义通讯协议,即使最简单的通过串口发送文本聊天的程序。

通常是在当一方按下回车时,将其所数据的文本连同换行符发给另一方。在这个通讯事例中,协议桢是通过换行符界定的,每一桢数据都被换行符隔开,这样就很容易识别出通讯双发发送的信息。

在以上的例子中,可以用WriteLine()来发送数据,用ReadLine()来读取数据。WriteLine发送完数据后,会将换行符作为数据也发送给对方。ReadLine()读取数据时,直至遇到一个换行符,然后返回一个字符串代表一行信息。换行符可以通过SerialPort 的属性NewLine来设置。一般地,Windows将CrLn作为换行符,而在Linux下,换行符则只用一个Ln表示。

ReadLine()方法是阻塞的,直至遇到一个换行符后返回。在读取数据时,如果一直没有遇到换行符,那么在等待ReadTimeout时间后,抛出一个TimeoutException。默认情况下,ReadTimeout为InfiniteTimeout。这样,ReadLine一直处于阻塞状态,直至有新一行数据到达(这段话可以完美说明阻塞问题)。

WriteLine()方法也是阻塞的,如果另一方不能及时接收数据,就会引起TimeoutException异常。

由于ReadLine()和WriteLine()方法都是阻塞式的,在程序使用SerialPort 进行串口通讯时,一般应该把读写操作交由其他线程处理,避免因为阻塞而导致程序不响应。

论坛的说法:https://bbs.csdn.net/topics/350246033
ReadTimeOut的意思是从有数据开始,读取的时间超过这个设置的值,则引发异常,比如是某些驱动级原因导致读取时间过长导致的,并不是说ReadByte,ReadTo,ReadLine的超时时间,这些方法本身是一定会同步阻塞等结果的,你可以先判断一下,比如
if(serial.BytesToRead>0) b = serial.ReadByte();
或是用
serial.ReadExisting()
读取到一个缓存,再处理替代ReadLine或ReadTo。
替代都是发生在你的信源(对你程序的发送方)可能会正常情况的缺失\r\n或你指定字符的情况。

四、总结
好了,到这里为止,超时问题可以完美的被解决了。以上叙述可以总结为:

1、收发往往放在不同的线程,以防止被阻塞的。本文的接收采用事件方式,发送在主线程。

2、你可以不用ReadLine方法,用以下方法就能实现正常的接收:

            byte[] ReDatas = new byte[SerialPortObj.BytesToRead];
            SerialPortObj.Read(ReDatas, 0, ReDatas.Length); //从串口读取数据

3、当然,若你要用ReadLine方法,最好是将ReadTimeout设置为无穷大,并且能够识别 换行符。这样你能一直等待接收信息了,不会超时抛出异常了。
————————————————
版权声明:本文为CSDN博主「沧海箫剑」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xpj8888/article/details/84628762

你可能感兴趣的:(C# SerialPort串口ReadTimeout 超时异常。“System.TimeoutException”类型的未经处理异常在 System.dll 中发生 其他信息: 操作已超时。)