views:

618

answers:

2

Hi,

I have an ugly piece of Serial Port code which is very unstable.

void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    Thread.Sleep(100);
    while (port.BytesToRead > 0)
    {
        var count = port.BytesToRead;

        byte[] buffer = new byte[count];

        var read = port.Read(buffer, 0, count);

        if (DataEncapsulator != null)
            buffer = DataEncapsulator.UnWrap(buffer);


       var response = dataCollector.Collect(buffer);

       if (response != null)
       {
           this.OnDataReceived(response);
       }

       Thread.Sleep(100);
    }    
}

If I remove either Thread.Sleep(100) calls the code stops working.

Of course this really slows things down and if lots of data streams in, it stops working as well unless I make the sleep even bigger. (Stops working as in pure deadlock)

Please note the DataEncapsulator and DataCollector are components provided by MEF, but their performance is quite good.

The class has a Listen() method which starts a background worker to receive data.

public void Listen(IDataCollector dataCollector)
{
    this.dataCollector = dataCollector;
    BackgroundWorker worker = new BackgroundWorker();

    worker.DoWork += new DoWorkEventHandler(worker_DoWork);
    worker.RunWorkerAsync();
}

void worker_DoWork(object sender, DoWorkEventArgs e)
{
    port = new SerialPort();

    //Event handlers
    port.ReceivedBytesThreshold = 15;
    port.DataReceived += new SerialDataReceivedEventHandler(port_DataReceived);

    ..... remainder of code ...

Suggestions are welcome!

Update: *Just a quick note about what the IDataCollector classes do. There is no way to know if all bytes of the data that has been sent are read in a single read operation. So everytime data is read it is passed to the DataColllector which returns true when a complete and valid protocol message has been received. In this case here it just checks for a sync byte, length , crc and tail byte. The real work is done later by other classes. *

Update 2: I replaced the code now as suggested, but still there is something wrong:

void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
        var count = port.BytesToRead;

        byte[] buffer = new byte[count];

        var read = port.Read(buffer, 0, count);

        if (DataEncapsulator != null)
            buffer = DataEncapsulator.UnWrap(buffer);

        var response = dataCollector.Collect(buffer);

        if (response != null)
        {
            this.OnDataReceived(response);
        }     
}

You see this works fine with a fast and stable connection. But OnDataReceived is NOT called every time data is received. (See the MSDN docs for more). So if the data gets fragmented and you only read once within the event data gets lost.

And now I remember why I had the loop in the first place, because it actually does have to read multiple times if the connection is slow or unstable.

Obviously I can't go back to the while loop solution, so what can I do?

+1  A: 

My first concern with the original while-based code fragment is the constant allocation of memory for the byte buffer. Putting a "new" statement here specifically going to the .NET memory manager to allocate memory for the buffer, while taking the memory allocated in the last iteration and sending it back into the unused pool for eventual garbage collection. That seems like an awful lot of work to do in a relatively tight loop.

I am curious as to the performance improvement you would gain by creating this buffer at design-time with a reasonable size, say 8K, so you don't have all of this memory allocation and deallocation and fragmentation. Would that help?

private byte[] buffer = new byte[8192];

void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    Thread.Sleep(100);
    while (port.BytesToRead > 0)
    {
        var count = port.BytesToRead;

        var read = port.Read(buffer, 0, count);

        // ... more code   
    }
}

My other concern with re-allocating this buffer on every iteration of the loop is that the reallocation may be unnecessary if the buffer is already large enough. Consider the following:

  • Loop Iteration 1: 100 bytes received; allocate buffer of 100 bytes
  • Loop Iteration 2: 75 bytes received; allocate buffer of 75 bytes

In this scenario, you don't really need to re-allocate the buffer, because the buffer of 100 bytes allocated in Loop Iteration 1 is more than enough to handle the 75 bytes received in Loop Iteration 2. There is no need to destroy the 100 byte buffer and create a 75 byte buffer. (This is moot, of course, if you just statically create the buffer and move it out of the loop altogether.)

On another tangent, I might suggest that the DataReceived loop concern itself only with the reception of the data. I am not sure what those MEF components are doing, but I question if their work has to be done in the data reception loop. Is it possible for the received data to be put on some sort of queue and the MEF components can pick them up there? I am interested in keeping the DataReceived loop as speedy as possible. Perhaps the received data can be put on a queue so that it can go right back to work receiving more data. You can set up another thread, perhaps, to watch for data arriving on the queue and have the MEF components pick up the data from there and do their work from there. That may be more coding, but it may help the data reception loop be as responsive as possible.

JeffFerguson
A: 

If you want to write the data to a file and the serial port stops every so often this is a simple way to do it. If possible make your buffer large enough to hold all the bytes that you plan to put in a single file. Then write the code in your datareceived event handler as shown below. Then when you get an oportunity write the whole buffer to a file as shown below that. If you must read FROM your buffer while the serial port is reading TO your buffer then try using a buffered stream object to avoid deadlocks and race conditions.

private byte[] buffer = new byte[8192]; var index = 0;void port_DataReceived(object sender, SerialDataReceivedEventArgs e){    index += port.Read(buffer, index, port.BytesToRead);} void WriteDataToFile(){binaryWriter.Write(buffer, 0, index); index = 0;}