提供异步串口通信

目前,我们的应用程序通过串口连接到Arduino。 我们发送一些ASCII格式的命令,并得到相同的回报。 为此,我们有一个命令队列,一个专门用于将这些命令写入端口的线程,以及一个专门用于读取和处理所有传入回复的线程。 类本身负责调度回复,这给了它太多的责任(应该只负责端口操作,而不是业务逻辑)。

我们宁愿以异步方式执行此操作。 系统中的任何内容都可以发送带有回调函数和超时的命令。 如果串口得到正确答复,则调用回调函数。 否则,它会超时并可能调用第二个回调(或者可能是带有succeeded?标志的单个回调)。

但是,我们只使用异步方法(特别是在Web操作中),而不是编写这样的系统。 谁能给我们一些关于如何进行的指示?

我们当前的计划是存储这些命令的队列。 在任何回复时,如果找到相关联的命令(通过比较ASCII值),它将被出列并执行回调。 计时器将定期检查超时,出队并执行适当的回调。 这似乎是一个简单的解决方案,但支持这一点的代码数量正在大幅增加,我们希望确保没有更好的内置解决方案或最佳实践。

编辑 :为了进一步澄清,这个特定的类是一个单例(无论好坏),还有许多其他线程可以访问它。 例如,一个线程可能想要请求传感器值,而另一个线程可能正在控制电机。 这些命令及其相关的回复不是以线性方式发生的; 时机可能会逆转。 因此,传统的生产者 – 消费者模式是不够的; 这更像是一个调度员。

例如,让我们调用这个单例类ArduinoThread A正在运行,并且想要发送命令"*03" ,因此它调用Arduino.Instance.SendCommand("*03") 。 同时, Thread B发送命令"*88" ,这两个命令都是近实时发送的。 一段时间之后, ArduinoSerialPort.Read()线程获取*88的回复然后回复*03 (即以相反的顺序发送它们)。 我们如何允许Thread AThread B正确阻止等待特定的回复进来? 我们假设我们将在每个线程中使用AutoResetEvent ,并使用异步回调让我们设置它。

如果性能是您所追求的,并且在最佳级别上是异步,我建议您查看完成端口。 这是最终隐藏在Windows内核中的内容,它非常棒。 当我使用它们时,我使用了C ++,甚至因为它发现了一个内核错误, 我仅限于该语言。

我在CodeProject上看过这篇文章 ,可能值得探讨一下,你可以在哪里进一步了解你的想法和/或使用那里的代码。

完成端口的性质是处理回调。 也就是说,通常,您将一个请求“放入”队列中,当某些内容落在那里时,将读取请求并读取指定的回调。 事实上,这是一个队列,但就像我说的那样,处于最低(可管理)的水平(在接近金属之前)。

编辑:我已经编写了一种带有完成端口的FTP服务器/客户端测试实用程序,因此基本过程是相同的 – 以可编排的方式读取和写入命令。 希望能帮助到你。

编辑#2:好的,根据您的反馈和评论,我会做什么。 我会有一个“传出队列”, ConcurrentQueue 。 您可以通过出列每个消息来使用单独的线程来发送消息。 请注意,如果您希望它更“安全”,我建议窥视该消息,发送它,然后将其出列。 无论如何,Message类可以是内部的,看起来像这样:

 private class Message { public string Command { get; set; } ... additonal properties, like timeouts, etc. ... } 

在单例类(我称之为CommunicationService )中,我还有一个ConcurrentBag> 。 这就是现在开始的乐趣:o)。 当一个单独的关注想要做某事时,它会自我注册,例如,如果你有一个TemepratureMeter我会让它做这样的事情:

 public class TemperatureMeter { private AutoResetEvent _signal = new AutoResetEvent(false); public TemperatureMeter { CommunicationService.AddHandler(HandlePotentialTemperatureResponse); } public bool HandlePotentialTemperatureResponse(Response response) { // if response is what I'm looking for _signal.Set(); // store the result in a queue or something =) } public decimal ReadTemperature() { CommunicationService.SendCommand(Commands.ReadTemperature); _signal.WaitOne(Commands.ReadTemperature.TimeOut); // or smth like this return /* dequeued value from the handle potential temperature response */; } } 

现在,在您的CommunicationService中,当您收到回复时,您只需要一个

 foreach(var action in this._callbacks) { action(rcvResponse); } 

瞧,关注点分离。 它会更好地回答你的问题吗?

另一种可能的策略是,结合消息和回调,但让Callback为Func ,并且调度程序线程检查从Func返回的结果是否为真,然后处理此回调。

如果您使用4.0,则更好的选择是使用BlockingCollection ,对于旧版本,使用QueueAutoResetEvent 。 因此,当添加项目和消费者线程时,您将收到通知 ,然后只需使用它。 在这里,我们正在使用推送技术,在您当前的实现中,您每次询问是否有任何数据时都使用轮询技术。

示例: 4.0

 //declare the buffer private BlockingCollection _buffer = new BlockingCollection(new ConcurrentQueue()); //at the producer method "whenever you received an item": _messageBuffer.Add(new Data()); //at the consumer thread "another thread(s) that is running without to consume the data when it arrived." foreach (Data data in _buffer.GetConsumingEnumerable())// or "_buffer.Take" it will block here automatically waiting from new items to be added { //handle the data here. } 

示例:其他“较低”版本:

 private ConcurrentQueue _queue = new ConcurrentQueue(); private AutoResetEvent _queueNotifier = new AutoResetEvent(false); //at the producer: _queue.Enqueue(new Data()); _queueNotifier.Set(); //at the consumer: while (true)//or some condition { _queueNotifier.WaitOne();//here we will block until receive signal notification. Data data; if (_queue.TryDequeue(out data)) { //handle the data } }