serialport多线程未经请求的消息

时间:2012-05-07 21:08:46

标签: c# multithreading serial-port

我有C#多线程应用程序,必须使用SerialPort与硬件连接。

该程序主要是命令响应序列,但由于内部错误,硬件可以发送未经请求的“RESET”消息,此时软件必须通过发送一系列设置某些值的命令重新初始化它。

多个线程(来自线程池)可以尝试执行TakeSampleNow()

public class ALComm
{
    private readonly AutoLoaderManager _manager;
    private readonly AutoResetEvent dataArrived = new AutoResetEvent(false);
    private SerialPort _alPort;
    private string _alResponse;

   .... Code to init _alPort and attach datareceived event etc

   public void TakeSampleNow()
    {
        if (Monitor.TryEnter(_alPort, 1000)) //let's wait a second
        {
            _manager.MessageList.Enqueue("Try sampling");
            try
            {
               Send("Command1");
               string response = Receive();

               switch(response)
                {
                   case "X": blah blah..
                   case "Y": blah blah..
                } 

               Send("Command2");
               string response = Receive();

               while(response != "OK")
                 {
                   Send("Command3");
                   string response = Receive();
                   Send("Command2");
                   string response = Receive();
                 }
            }
            finally
            {
                Console.WriteLine("Releasing port");
                //Thread.CurrentThread.Priority = ThreadPriority.Normal;
                Monitor.Exit(_alPort);
            }
        else
        {
            _manager.MessageList.Enqueue("Port is busy!!!");
        }
   }

    public string Receive()
    {
        string inString = null;

            dataArrived.WaitOne(1000);
            inString = _alResponse;

        return inString;
    }

    private void AlPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        _alResponse = _alPort.ReadLine();

        //if (_alResponse.ToUpper().Contains("RESET"))
        //{
        //    _alState = AlState.Reset;
        //    TryInitialize();
        //}

        dataArrived.Set();            
    }

    private void TryInitialize()
    {
        Monitor.Enter(_alPort);       //lock so other threads do not access samplenow during initialization
        try
        {
            string response;

            Console.WriteLine("Initializing ... The AutoLoader");
            _alPort.DiscardInBuffer();

            Send("CommandX");
            response = Receive();

            --- blah blah

           _alResponse = "";
        }
        finally
        {
            Monitor.Exit(_alPort);
        }
    }

我可以检查datareceived事件中的响应并等待TryInitialize()中的锁定以及TakeSampleNow中的其他线程释放锁定,如果_alResponse包含“RESET”并且它确实返回,我将不得不检查每个响应从方法。这让它更复杂。

有关如何更好地做到这一点的任何建议。我相信它可以是状态机,但我无法将其概念化。

2 个答案:

答案 0 :(得分:1)

您不希望多个线程尝试读取您的串行端口。您应该有一个单独的线程,除了读取端口之外什么也不做。当它获取一些数据时,它会将消息放入队列或类似的数据结构中,这些数据结构可由多个样本线程处理。这样,您的单个读取器线程就可以可靠地查找并响应RESET消息。

答案 1 :(得分:1)

您没有提供协议的详细信息 - 您没有说明命令/ rsponse对是否可以重叠,如果是,则说明响应如何与命令匹配。

您应该可以使用状态引擎执行此操作。使用自己的线程运行状态机,该线程在BlockingCollection上等待事件。您还需要一个'SerialRecv'线程来运行您的协议并将传入的字节解析为消息。

我只使用一个'SerialEvent'类将事件传递到SM队列中。该类应该有一个枚举描述RX缓冲区中的事件和成员,TX数据,分析数据,数据可以集合,异常/ errorMess场TX字符串 - 需要任何事件或转发的目的,(如一切SM可能。将完成的请求/响应转发给显示器或记录器。)

我能直接想到的一些事件:EsmNewRequestResponse,EsmRxData,EsmResetRx

事件枚举可以作为某些阶段具有SM未使用的其他值,例如:EsmError,EsmLog,EsmDisplay。

如果需要超时,可以通过超时SM输入队列中的take()来生成一个。

是的,我遗漏了一些东西。

如果多个线程同时发出SerialEvent实例,那么SM将在处理第一个串行事件时获得新的SerialEvent。 SM将需要另一个队列/双端队列来保存等待处理的SerialEvents。由于BlockingCollection / thread对SM进行序列化,因此该“挂起”队列不必是线程安全的。在任何请求/响应完成后,SM应检查此待处理队列,以查看是否还有另一个要处理的队列。

为了从多个线程同步处理请求/响应,请求线程必须具有等待的东西。 SerialEvent类中的AutoResetEvent可以。向系统提交SerialEvent将排队SerialEvent实例并等待AutoResetEvent。当实例的处理完成时(即收到响应,错误或超时),SM将设置事件,并且原始线程将在其SerialEvent实例填入数据的情况下运行。

接下来 - SerialEvent类正朝着可能更好地集中它们而不是不断创建/ CG的方向发展。这需要另一个BlockingCollection作为池。