Providing Asynchronous Serial Port Communication

前端 未结 2 543
慢半拍i
慢半拍i 2020-12-15 14:55

Currently our application connects to an Arduino over a serial port. We send some ASCII-formatted commands, and get the same in return. To do this, we have a queue of comman

相关标签:
2条回答
  • 2020-12-15 15:34

    A better choice if you are using 4.0 is to use a BlockingCollection for that, for older versions use a combination from a Queue<T> and AutoResetEvent. So you will be notified when item is added and on the consumer thread and then just consume it. here we are using a push technology where at your current implementation you are using a poll technology "each time you are asking if there is any data".

    Example: 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.
    }
    

    Example: other "lower" versions:

    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
        }
    }
    
    0 讨论(0)
  • 2020-12-15 15:37

    If performance is what you're after, and async at its finest level, I suggest looking into Completion Ports. This is what is ultimately underneath, hidden in the Windows Kernel, and it's awesome. When I used them, I used C++, even found a Kernel bug because of it, but I was limited to the language.

    I've seen this article on CodeProject which might be worth exploring to see where you can take your idea further and/or use the code that's there.

    The nature of Completion ports is to work on callbacks. That is, in general, you "put" a request in the queue, and when something lands there, the request is read and the callback specified is read. It is in fact, a queue, but like I said, at the lowest (manageable) level (before getting almost on metal).

    EDIT: I've written a sort of a FTP server/client testing utility with Completion ports, so the base process is the same - reading and writing of commands in a queuable fashion. Hope it helps.

    EDIT #2: Ok, here's what I would do, based on your feedback and comments. I would have an "outgoing queue", ConcurrentQueue<Message>. You can have a separate thread for sending messages by dequeueing each message. Note, if you want it a bit more "safe", I suggest peeking at the message, sending it, then dequeuing it. Anyway, the Message class can be internal, and look something like this:

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

    In the singleton class (I'll call it CommunicationService), I'd also have a ConcurrentBag<Action<Response>>. This is now where the fun starts :o). When a separate concern wants to do something, it registeres itself, for example, if you have a TemepratureMeter I would have it do something like this:

    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 */;
       }
    
    }
    

    And now, in your CommunicationService, when you receive a response, you simply to a

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

    Voila, separation of concerns. Does it answer your question any better?

    Another possible tactic would be, to couple message and callback, but having the Callback be a Func<Response, bool> and the dispatcher thread checks if the result returned from the Func is true, then this callback is disposed.

    0 讨论(0)
提交回复
热议问题