Search code examples
c#serial-portserial-communicationautoresetevent

C# Serial communication custom function with AutoResetEvent timing out after first call


I'm trying to use serial communication to talk to a piece of custom hardware. The hardware has built in methods which can be called with a command.

To send a command and read the output of the function (from the hardware) I am using the AutoResetEvent event as shown below:

private string receiveMessage(string command)
{
    string rxMessage = "Error";

    var mre = new AutoResetEvent(false);
    var buffer = new StringBuilder();

    serialPort.DataReceived += (s, e) =>
    {
        buffer.Append(serialPort.ReadExisting());
        if (buffer.ToString().IndexOf("\r\n") >= 0)
        {
            rxMessage = buffer.ToString();

            mre.Set();
            buffer.Clear();
        }
    };

    var responseTimeout = TimeSpan.FromSeconds(10);

    try
    {
        serialPort.WriteLine(command);
        if (!mre.WaitOne(responseTimeout))
        {
            rxMessage = "Did not receive response";
        }
    }
    catch (TimeoutException)
    {
        rxMessage = "Write took longer than expected";
    }
    catch
    {
        rxMessage = "Failed to write to port";
    }

    return rxMessage;
}

This method functions perfectly on the first call. But, if it is called a second time, the serial port times out and the method returns "Did not receive a response".

Am I missing something or have I created the receieveMessage() method incorrectly?

Many thanks.


Solution

  • This is most likely due to not unregistering the event handler. So the second time you call receiveMessage you will have to different event handlers. The first one will read all the data and set its auto-reset event. By the time the second event handler is called there is no data to read, and its auto-reset event will timeout.

    To fix this you have a few alternatives. One is to unregister the event handler. To do this you need to either convert the lambda into a delegate object, i.e.

    SerialDataReceivedEventHandler myHandler = (s, e) => {...}
     serialPort.DataReceived += myHandler ;
    ...
    serialPort.DataReceived -= myHandler ;
    

    Or replace your lambda with a local function.

    Another alternative would be to change your method to a class, so that you only ever register the DateReceived event once.

    Note that I think you have a race condition. If the data is received before WaitOne is called the event will not be set, and you will timeout. Using a ManualResetEvent/ManualResetEventSlim that keeps it signal state until explicitly reset should avoid this issue.