Question

I have an event handler that gets called anytime data is received on a serial port, once this event is called I process an individual byte of data until all data is processed.

My question is, how do I make sure that this IBSerialPort_DataReceived event handler doesn't get called again asynchronously while I am stilling processing bytes from the first time it was called, ie I want to guarantee the ProcessByte method is only ever executing on one thread at a time.

void IBSerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
  while (BytesToRead > 0)
  {
    ProcessByte((byte)ReadByte());
  }
}
Was it helpful?

Solution 2

First read the documentation on the library you are using - can the event be raised asynchronously? If not then you are done.

UPDATE: Apparently this particular event cannot be raised concurrently, see Hans Passant's answer.

If it can, one strategy is to use a lock:

object myLock = new object();
void IBSerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    lock(myLock)
    {
        while (BytesToRead > 0)
        {
            ProcessByte((byte)ReadByte());
        }
    }
}

Take care now that you are using locks, if you lock on the same instance elsewhere you can get dead locks or if you hold the same lock for a while you will hold up processing...

Aside: generally the documentation will tell you if the event can raised concurrently so you will have to deal with re-entry, e.g. System.Threading.Timer's Tick event, though this is not always the case, e.g. the serverly lacking WinRT documentation on DatagramSocket's MessageReceived event.

OTHER TIPS

This is already interlocked inside the SerialPort class. There is an internal lock that ensures the DataReceived event cannot fire again if your event handler is still running.

You can see the relevant code in the Reference Source, I'll reproduce it here:

    private void CatchReceivedEvents(object src, SerialDataReceivedEventArgs e)
    {
        SerialDataReceivedEventHandler eventHandler = DataReceived;
        SerialStream stream = internalSerialStream;

        if ((eventHandler != null) && (stream != null)){
            lock (stream) {
                // SerialStream might be closed between the time the event runner
                // pumped this event and the time the threadpool thread end up
                // invoking this event handler. The above lock and IsOpen check
                // ensures that we raise the event only when the port is open

                bool raiseEvent = false;
                try {
                    raiseEvent = stream.IsOpen && (SerialData.Eof == e.EventType || BytesToRead >= receivedBytesThreshold);
                }
                catch {
                    // Ignore and continue. SerialPort might have been closed already!
                }
                finally {
                    if (raiseEvent)
                        eventHandler(this, e);  // here, do your reading, etc.
                }
            }
        }
    }

Note the lock (stream) statement in this code. You can also see that your DataReceived event handler won't be called unless something is received. And that you have to watch out for the SerialData.Eof event type, that has tripped up a fair number of programmers.

You don't have to help.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top