0

我有 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”并且它是否确实从该方法返回,我将不得不检查每个响应. 这使它更加复杂。

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

4

2 回答 2

1

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

于 2012-05-07T21:37:55.030 回答
1

您没有提供协议的太多细节 - 您没有说命令/响应对是否可以重叠,如果可以,响应如何与命令匹配。

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

我将只使用一个“SerialEvent”类将事件传送到 SM 队列中。该类应该有一个枚举来描述事件和 rx 缓冲区的成员、txData、解析的数据、用于组装 tx 字符串的数据、异常/errorMess 字段 - 任何事件或转发目的所需的一切,(例如,SM 可能将完成的请求/响应转发到显示器或记录器)。

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

事件枚举可能与某些阶段一样,具有 SM 未使用的其他值,例如:EsmError,EsmLog,EsmDisplay。

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

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

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

要同步处理来自多个线程的请求/响应,请求线程必须有东西等待。SerialEvent 类中的 AutoResetEvent 就可以了。向系统提交 SerialEvent 将使 SerialEvent 实例排队并等待 AutoResetEvent。当实例处理完成时(即收到响应、错误或超时),SM 将设置事件,并且源线程将继续运行,其 SerialEvent 实例填充数据。

接下来 - SerialEvent 类正朝着将它们汇集而不是不断创建/CG 更好的地步。那将需要另一个 BlockingCollection 来充当池。

于 2012-05-07T21:57:11.313 回答