串口通信--处理子线程中的错误

        在我的前两篇博客中,给大家介绍了在串口通信中创建子线程Read方法,来读取串口中的数据,以及如何控制我们Read方法读取的次数。但还有很重要的一点就是在这个过程中,Read方法是Send方法开启的一个新的线程,在这个处理过程中,如果出现了错误怎么办,很多人第一想到的是使用Try{}Catch{}方法来捕捉,但是我想说的是在.net 中,主线程是无法捕捉到子线程的错误的。首先看一段C#代码:运行后发现主线程通过try{}catch{}是不能扑捉子线程中的抛出来的异常:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                System.Threading.Thread thread = new System.Threading.Thread(new Program().run);
                thread.Start();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            Thread.Sleep(1000);
        }
        public void run()
        {
            throw new Exception();
        }
    }
}

      为什么呢?

      首先需要了解异常的实现机制:异常的实现机制是严重依赖与线程的栈的。每个线程都有一个栈,线程启动后会在栈上安装一些异常处理帧,并形成一个链表的结构,在异常发生时通过该链表可以进行栈回滚,如果你自己没有安装的话,可能会直接跳到链表尾部,那可能是CRT提供的一个默认处理帧,弹出一个对话框提示某某地址内存错误等,然后确认调试,取消关闭。

      所以说,线程之间是不可能发生异常处理的交换关系的。

      但是在实际的程序设计中,会牵涉到扑捉子线程的异常,那么该怎样解决这个问题呢?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace ConsoleApplication1
{
    class Program
    {
        private delegate void ExceptionHandler(Exception ex);
        private static ExceptionHandler exception;
        private static void ProcessException(Exception ex)
        {
            Console.WriteLine(ex.Message);
            Console.Read();
        }
        static void Main(string[] args)
        {
            exception = new ExceptionHandler(ProcessException);
            System.Threading.Thread thread = new System.Threading.Thread(new Program().run);
            thread.Start();

            Thread.Sleep(1000);
        }
        public void run()
        {
            try
            {
                throw new Exception("子线程异常");
            }
            catch (Exception ex)
            {
                if (exception != null)
                {
                    exception(ex);
                }
            }
        }
    }
}

      上面使用委托的方式,间接的解决了:把子线程中的异常信息交个主线程的一个方法去执行。(通过委托方式)

在我们的Read方法中,同样采用这样的处理方法,当然也不能忘了我们的目的是要把我们的东西全部封装起来,那怎么才能保证我们的错误在窗体中显示呢。我们我们throw出去的错误也没有人接收啊。所以我们完全可以发展一下这个方法,我们把捕捉到的错误交给我们原来写好的IDeal接口来处理,因为我们已经决定要想使用我们写好的协议来与串口进行通信,就必须实现IDeal方法这样我们才能知道我们返回的数据谁去处理,这两个完全是同样的道理,然后看我们的Read方法:

    Private ideal As IDeal
    Private Sub Read()
        Try
            Dim str As String = "" ‘定义临时保存返回数据的字符串变量  
            Dim buf() As Byte ‘定义保存返回数据类型为byte的变量  
            Dim dataCount As Integer = _serialPort.BytesToRead ‘定义保存缓冲区数据的变量  
            Dim dataCountNew As Integer = 0 ‘定义保存最新的需要读取的缓冲区数据的变量  
            Dim CountTimes As Integer = 0 ‘尝试从缓冲区读数据的次数  
            ‘此处是本段的核心,1000*sleep(5)表示自己定义的最长等待串口返回数据的时间  
            ‘如果第一次取得的缓冲区的需要读取的数据为0,则线程暂停5ms(当然也可以自己定,数字越小性能未必会更好。合适就行)。一旦读到数据就执行下面的代码。或者循环完毕,也不能读到数据就会抛出异常。  
            While (dataCount = 0 And CountTimes < 1000)
                Thread.Sleep(5) ‘线程休眠5ms  
                dataCount = _serialPort.BytesToRead ‘重新取出缓冲区待读到的数据  
                ‘如果取的的数据还为0  
                If (dataCount <= 0) Then
                    ‘读取的次数加1  
                    CountTimes = CountTimes + 1
                    Continue While ‘继续执行循环  
                Else ‘否则退出循环  
                    Exit While
                End If
            End While
            ‘如果等待1000*5ms=5秒以后仍然没有读到数据  
            If dataCount = 0 Then
                ‘抛出异常  
                Throw New Exception("连接分机超时,请查看分机是否启动,或检查通讯网络!!")
            End If
            ‘如果读到数据,首先休眠500ms(这个数据一般是根据返回的数据的次数和晶振的频率,以及数据传输的一些时间设定,如果查询数据对于时间的性能要求不高可以适当的加长),再次读取缓冲区待读数据,和刚才读到的数据进行比较,如果相等,则说明读到的数据已经完整。可以进行处理。  
            While True
                Thread.Sleep(500)
                dataCountNew = _serialPort.BytesToRead ‘重新读取缓冲区数据的个数。  
                ‘如果不等则把这次读到的缓冲区数据的个数作为最新的数据保存  
                If dataCount <> dataCountNew Then
                    dataCount = dataCountNew
                Else ‘如果相同,说明数据返回完全,退出循环  
                    Exit While
                End If
            End While
            For i = 0 To dataCount ‘这时的循环,会根据实际情况的不同而不同。  
                Dim d As Integer
                d = _serialPort.ReadByte ‘从串口中读取一个字节的数据,如果设置了读取超时时间,在规定的时间未读到数据会触发超时错误。      
                str += Convert.ToString(d, 16).PadLeft(2, "0")
            Next
            buf = GetByte(str) ‘将收到的数据转换为字节数组。  
            ‘根据协议,处理收到的数据      
        Catch ex As Exception
            Me.DealEX(ex)‘当然这也可以用委托(但这样写也没有问题)
        End Try
    End Sub
    Public Sub DealEX(ByVal ex As Exception)
        ideal.DealEX(ex)
    End Sub
      这样我们的Read方法就更加的健壮了。

串口通信--处理子线程中的错误

上一篇:SQL中合并查询结果集


下一篇:关于UDPThread这个类的使用方法