0
votes

I'm building an app in C# that runs with Excel, the end game is to allow users to click a button on an excel sheet, which then sends some commands down the serial port, and then reads back data.

This is done and works fine.

My question is, how can I check to make sure that no events / functions are being ran on the serialport_DataReceived; event before the application closes, as if they are then I will get an exception crash -

The I/O operation has been aborted because of either a thread exit or an application request.

So, in a way I need to run some sort of clean up stating that is the serial port is still gathering data, wait until that has finished before closing down.

This is my code so far;

     while (!dataCollected)
     {
        if (data.Contains(carrier)) // If the read data doesn't contain end carriage return, then retry until it does
        {
             if (data.Contains(resultMessage)) // Check to make sure it has the result message - this guarantees a fully built read data with no missing bytes 
             {
                 ParseIncomingDataToExcel(data); // put the string into a specfic excel row
                 dataCollected = true; // exits out of the while loop
             }
        }
        else // Carriage not found, data loss - try again
        {
            data = wrench.ReadLine();
        }
}


if (dataCollected)
    SetControlPropertyValue(button, "Enabled", true); // Invoke to turn the button enabled to true

So basically, you send a command via serial and then get an output. If you try and exit during this process then it will give an exception (I know this will happen, I would rather put the code in to stop it from happening rather then just putting it in a try block

private void Sheet1_Shutdown(object sender, System.EventArgs e)
{
     dataCollected = true;
     wrench.Close();
}

This makes the program hang when trying to close a connection on the serial port... If I remove the wrench.close - then the above exception is show (As I expected).

--EDIT--

I've tried declaring my boolean values in my while loop to a global volatile type, but I just have a bad feeling with using something like this, seems like a hack (and still doesn't work, just hangs...)

2

2 Answers

4
votes

Two problems. First one is your SetControlPropertyValue() method. It is an Evil Method that is hiding a Control.Invoke() call. This is very likely to cause deadlock. The SerialPort.Close() method cannot close the port until all event handlers stopped running. But the Invoke() call cannot complete until the UI thread goes idle. It isn't idle, it is busy trying the close the port. Can't finish the event handler, can't close the port, deadlock city. You must use BeginInvoke() instead.

Second problem is that you are jerking the floor mat while the serial port is busy receiving data. .NET wants to let you know about this, the ReadLine() call cannot complete normal. It of course does so by throwing an exception. You are definitely losing elegant points here by reading data rather haphazardly. A well-implemented serial port protocol asks the device to send data, the device responds with a single data item. It sounds like your device is just emitting data constantly. If you have no decent control over it, say by turning the RtsEnable handshake signal off, then the exception is probably your least objectionable solution.

Do watch out for a bug, that // data loss - try again comment looks very unhealthy. You try again by just clearing whatever response buffer you use, the DataReceived event will fire again without your help. Using ReadLine() is often an easy way to let SerialPort deal with getting the full response from the device, but if you can't be sure that it always completes then you do need to set the ReadTimeout property. Don't set it too low, 10 seconds is a safe bet. How you handle the TimeoutException is up to you.

2
votes

I think your error comes from the fact that you're still waiting for more data coming from your serial.ReadLine(). Trouble with this serial.ReadLine() is its blocking nature. If you intend a serial.Close() or serial.Dispose() while it a serial.ReadLine() is still pending you'll end up in the situation you indicated.

After all missing carriage return seem to happen since you have this "Carriage not found, data loss - try again" case.

You'd better do a serial.Read(x-characters) which is not blocking and allow you to control when to stop reading so that you can close cleanly.