提供异步串口通信

时间:2011-08-02 18:33:01

标签: c# multithreading asynchronous serial-port

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

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

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

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

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

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

2 个答案:

答案 0 :(得分:3)

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

我在CodeProject上看过this article,可能值得探索一下,看看你可以在哪里进一步理解和/或使用那里的代码。

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

编辑:我编写了一种带有完成端口的FTP服务器/客户端测试实用程序,因此基本过程是相同的 - 在 queuable <中读取和写入命令/ em>时尚。希望它有所帮助。

编辑#2:好的,根据您的反馈和评论,这就是我要做的。我会有一个“传出队列”,ConcurrentQueue<Message>。您可以通过出列每个消息来使用单独的线程来发送消息。 注意,如果你想要它更“安全”,我建议偷看邮件,发送它,然后将它出列。无论如何,Message类可以是内部的,看起来像这样: / p>

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

在单身人士课程中(我称之为CommunicationService),我也有一个ConcurrentBag<Action<Response>>。这就是现在开始的乐趣: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);
}
Voila,关注点分离。它会更好地回答你的问题吗?

另一个可能的策略是,结合消息和回调,但让Callback为Func<Response, bool>并且调度程序线程检查从Func返回的结果是否为真,那么此回调是 dispos < / em>的

答案 1 :(得分:1)

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

示例:4.0

//declare the buffer
private BlockingCollection<Data> _buffer = new BlockingCollection<Data>(new ConcurrentQueue<Data>());

//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<Data> _queue = new ConcurrentQueue<Data>();
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
    }
}