none
SerialPort asynchronous communication

    Question

  • I want to communicate through a SerialPort with a device.  The device follows ModbusRTU protocol, so I will always know how many bytes I'm expecting back.  The DataReceived event is good in that it receives bytes on the buffer and I could store them; however, I'm expecting a set amount back.  So what I have right now for a Wait function is

    while(serPort.BytesInBuffer < expectedBytesToRead)

    {

        Sleep.Thread(50);

    }

    Now if I didn't add the Sleep.Thread, my CPU usage jumps incredibly high (~60%), and with the sleep its at about ~20%.  My receive function just calls the serial ports Read(buffer, 0, expectedBytesToRead) after the Wait loop is done.  This was just a quick draft of a wait, I plan to use a timeout and retry somewhere down the road.

    So my question is, after just learning about Rx, is it possible to mock a Wait that implements what I'm trying to do while at the same time uses very little CPU usage? 

    If you need anymore details, let me know, thanks.

    Thursday, July 12, 2012 4:51 PM

Answers

  • Why do you want to block/wait? The point of Rx (and SerialPort comms) is that it is asynchronous, so putting a blocking call in seems to be against the spirit of what each technology offers.

    perhaps you could do something like this (no idea if this even compiles) and then you will just get the all the bytes until you hit your expected count. 

    var bytes = Observable.Using(
        () => new SerialPort(strComPort),
        port => Observable.Create<byte>(
            o =>
            {
                serialPort.Open();
                var subscription = Observable
                    .FromEventPattern<SerialDataReceivedEventHandler, SerialDataReceivedEventArgs>(
                            handler => handler.Invoke,
                            h => serialPort.DataReceived += h,
                            h => serialPort.DataReceived -= h)
                    .SelectMany(_ =>
                            {
                                var toRead = serialPort.BytesToRead;
                                var buffer = new byte[toRead];
                                serialPort.Read(buffer, 0, toRead);
                                return buffer;
                            })
                    .Subscribe(o);
                return subscription;
            }));
    
    bytes.Take(expectedCountOfBytes)
         .Subscribe(b=>/*Do stuff with your byte array here*/);

    >If you need anymore details, let me know, thanks.

    Would help to hear more about what you are trying to achieve.

    Lee

    P.S. Do you realize that your CPU usage is high because you are pinning one thread in that while loop. The Thread.Sleep just lets it breath for 50ms. The Rx event paradigm will be much more efficient use of CPU


    Lee Campbell http://LeeCampbell.blogspot.com

    • Marked as answer by KLDev74 Monday, July 23, 2012 3:36 PM
    Friday, July 13, 2012 3:11 PM

All replies

  • Why do you want to block/wait? The point of Rx (and SerialPort comms) is that it is asynchronous, so putting a blocking call in seems to be against the spirit of what each technology offers.

    perhaps you could do something like this (no idea if this even compiles) and then you will just get the all the bytes until you hit your expected count. 

    var bytes = Observable.Using(
        () => new SerialPort(strComPort),
        port => Observable.Create<byte>(
            o =>
            {
                serialPort.Open();
                var subscription = Observable
                    .FromEventPattern<SerialDataReceivedEventHandler, SerialDataReceivedEventArgs>(
                            handler => handler.Invoke,
                            h => serialPort.DataReceived += h,
                            h => serialPort.DataReceived -= h)
                    .SelectMany(_ =>
                            {
                                var toRead = serialPort.BytesToRead;
                                var buffer = new byte[toRead];
                                serialPort.Read(buffer, 0, toRead);
                                return buffer;
                            })
                    .Subscribe(o);
                return subscription;
            }));
    
    bytes.Take(expectedCountOfBytes)
         .Subscribe(b=>/*Do stuff with your byte array here*/);

    >If you need anymore details, let me know, thanks.

    Would help to hear more about what you are trying to achieve.

    Lee

    P.S. Do you realize that your CPU usage is high because you are pinning one thread in that while loop. The Thread.Sleep just lets it breath for 50ms. The Rx event paradigm will be much more efficient use of CPU


    Lee Campbell http://LeeCampbell.blogspot.com

    • Marked as answer by KLDev74 Monday, July 23, 2012 3:36 PM
    Friday, July 13, 2012 3:11 PM
  • Thank you Lee, this is great news. 

    I am currently communicating with devices right now with in-house developed C++ .DLL that is using a mailbox communication method.  Somewhere within the .DLL it is burning up the CPU where I'm using ~60% CPU while monitoring 1 device and 100% when I'm monitoring two.  I hacked together some code using the .NET SerialPort and added the said while loop and had the same results as when I was using the .DLL.  I added a Thread.Sleep, like you said--as a breather, and I saw less CPU usage with obvious delays.  In fact, if I added a 250ms delay, I was monitoring 4 drives with ~4% CPU usage, it just was a little laggy.  I concluded that the .DLL was also burning the CPU in the same fashion that my code was, so I added a Thread.Sleep to the original project and I had better results like I did when I used SerialPort.

    What I'm trying to achieve is the ability to monitor multiple devices from an WPF application (MVVM design) both SerialPort and Socket.  Both types of communcations have the ability to be connected directly or on a network using slave addressing.  The .DLL is old (last touched in 2006) and uses mailboxing (LinkedLists), so I don't see it being very easy to port to C# .DLL.  I need communications to be efficient as possible and I was looking for solutions on how to do so, which is when I was told about Rx.  When all is said and done, I will be displaying the data using WPF controls. I hope this gives you a little insight.

    Monday, July 16, 2012 4:28 PM
  • I recently helped a friend with a C++/C# Rs323(serial port) uni project. I will try to find that code somewhere and post an example of how to do Rs323 + WPF + Rx without the SpinWait stuff. This all becomes much easier if you also know how many bytes you are expecting.

    Lee

    P.S I am in no way an expert on Network comms, but I am ok at Rx and WPF ;-)


    Lee Campbell http://LeeCampbell.blogspot.com

    Tuesday, July 17, 2012 12:25 PM
  • Thanks Lee, if you have any updates just post them here :)
    Monday, July 23, 2012 3:36 PM
  • Any luck finding that code for RS232 Lee?
    Friday, August 10, 2012 8:35 PM
  • @KLDev74

    Do this help?

    private static IObservable<IList<char>> DataStream(string strComPort)
    {
        return Observable.Create<IList<char>>(
            o =>
            {
                    var serialPort = new SerialPort(strComPort);
                    //Open the port
                    serialPort.Open();
                    //Register for any data received events.
                    var dataRecievedNotification = Observable.FromEvent<SerialDataReceivedEventHandler, SerialDataReceivedEventArgs>(
                        h=>serialPort.DataReceived+=h,
                        h=>serialPort.DataReceived-=h);
    
                    var dataStream = from dataRecieved in dataRecievedNotification
                                        from data in serialPort.ReadExisting()
                                        select data;
                            
                    return dataStream
                        .Buffer(KnownCharacterLength)
                        .Take(1)
                        .Subscribe(o);
            });
    }
    private static void LoadDataFromCOM1ToMyText()
    {
        DataStream("COM1")
            .Select((IList<char> chars) =>
            {
                //Here I convert the list of Chars to a string, but remain on the Other thread while I do so.
                var sb = new StringBuilder(chars.Count);
                sb.Append(chars.ToArray());
                return sb.ToString();
            })
            .SubscribeOn(_schedulerService.NewThread)
            .ObserveOn(_schedulerService.Dispatcher)
            .Subscribe(text =>
            {
                //Now I am on the dispatcher, I can set properties on my ViewModel/View etc...
                MyTextBox.Text = text;
            });
    }

    Where KnownCharacterLength is a const integer value (should be expectedBytesToRead).

    Hope that helps

    Lee


    Lee Campbell http://LeeCampbell.blogspot.com

    Tuesday, August 14, 2012 3:36 PM
  • Lee, thanks for the code sample.  I'm trying to integrate it however

    _schedulerService

    isn't declared.  Could you provide more details on how to declare it or where it is coming from please? .

    I also kind of want to clarify what I'm trying to do, because I can't really continue on a thread until I have all the bytes, I think.  So this is my pseudo code:

    desktop app requests a register value from device hardware to be read (for example register 0x100)

    it connects to the specified serial drive by opening the port

    then it builds a Modbus message using a .DLL

    I use the SerialPort to transmit the message to the device

    I then do a Receive to get the data off of the buffer

    I pass the byte buffer to the DLL that returns the msg in correct format

    I then strip the useful data out of the message and return it back to the variable requesting the data.

    My problem is that there isn't a long enough time between the transmit and receive because the device takes longer to handle the message and respond compared to how fast I get to the receive code.  It can take anywhere between 30-200ms depending on how many registers your're requesting to read.  Is Rx still a possibility or am I forced to use a Thread.Sleep?

    • Edited by KLDev74 Friday, August 17, 2012 2:42 PM added detail
    Friday, August 17, 2012 1:47 PM
  • Sorry! That is my own interface to wrap the static Rx class Scheduler. 

    http://introtorx.com/Content/v1.0.10621.0/16_TestingRx.html#SchedulerDI

    You could replace it with the following to get up and running

    .SubscribeOn(System.Reactive.Concurrency.Scheduler.NewThread)
    .ObserveOn(System.Reactive.Concurrency.DispatcherScheduler.Instance)

    HTH

    Lee


    Lee Campbell http://LeeCampbell.blogspot.com

    Friday, August 17, 2012 2:33 PM
  • can i do this with readLIne(); ?
    Wednesday, December 16, 2015 12:48 PM
  • You can try. It should work

    Console.Write("Name: ")
        name = Console.ReadLine()
    
        Console.WriteLine("Type QUIT to exit")
    
        While _continue
            message = Console.ReadLine()
    
            If stringComparer__1.Equals("quit", message) Then
                _continue = False
            Else
                _serialPort.WriteLine([String].Format("<{0}>: {1}", name, message))
            End If
        End While

    Thursday, December 24, 2015 10:36 AM