Problem with SerialPort

14,845

Solution 1

Sigh, this an age old problem with USB serial port emulators. Serial ports are devices that date from the stone age. They used to be screwed into the bus, no way to remove them while a program is using it without drawing sparks and billowing smoke. Stone age also includes the lack for any kind of plug-and-play support so that a program could detect that the device is suddenly gonzo.

Unfortunately, the majority of the crummy device drivers that emulate them just make them disappear, even though a program has the port opened. This works just about as well as jerking a flash drive out of the socket when Windows is writing files to it. There's a background worker thread that waits for notifications from the device driver so that it can generate the DataReceived, ErrorReceived and PinChanged events. That thread suffers a heart attack when the device suddenly disappears. You can't catch that, it is a thread that was started by the SerialPort class, you can't wrap it with try/catch.

By popular demand, Microsoft did something about it in .NET 4.0. Not actually sure what happens in that release. If you're stuck on an earlier release, the only reasonable thing you can do is tape a sign next to the USB slot: "Don't remove while in use!" Which inevitably makes somebody unplug the device at least twice to see what happens. After which they get bored with that and leave you in peace.

The very unreasonable workaround is an app.exe.config file with this content:

<?xml version ="1.0"?>
<configuration>
  <runtime>
    <legacyUnhandledExceptionPolicy enabled="1"/>
  </runtime>
</configuration>

Don't use it.

Solution 2

In my code, this happens as part of the Finalize() method on the BaseStream, which is called by the garbage collector.

As such, if one inherits the .NET SerialPort class and overrides the Open / Close, you can do the following:

During the Open, just call GC.SuppressFinalize(Me.BaseStream)

During the close, try to call GC.ReRegisterForFinalize(Me.BaseStream)

If the USB has been pulled out, this will throw an exception, moaning about access to the BaseStream. Either check the .IsOpen property before calling the GC, or wrap it in a Try Catch if you don't trust .IsOpen to return False every time...

That'll sort it out, your application will handle the fact that it's been pulled out, and it won't crash when you close.

I'm currently unable to make it then re-open the port if it's plugged back in, but at least there's some progress beyond a label saying don't touch...

Solution 3

You can inherit from SerialPort and override the Dispose() method to handle such exceptions. You can just gobble the exception (Dispose shouldn't be throwing anyway).

If you want to log the exception or handle it in some other way, you will have to check the disposing flag first. If it is false it means that Dispose was called by SerialPort's destructor and the object is already orphaned.

E.g.

    public class MySerialPort:SerialPort
    {
        protected override void Dispose(bool disposing)
        {
            try
            {
                base.Dispose(disposing);
            }
            catch (Exception exc )
            {                    
                if (disposing)
                {
                    //Log the error
                }
            }

        }
    }

Solution 4

I solved this problem by creating a separate process which handles the serial port. When the serial port is unplugged I restart the process. Now I can reconnect to a unplugged serial port device without restarting the main application.

TLDR; Create a separate process.

Example code

Serial port process code

static void Main(string[] args)
{
    using (var output = Console.OpenStandardOutput())
    using (var serialPort = new SerialPort(args[0], int.Parse(args[1])))
    {
        serialPort.Open();
        while (serialPort.IsOpen)
        {
            serialPort.BaseStream.CopyTo(output);
        }
    }
}

Main application

var options = new ProcessStartInfo()
{
    FileName = "Serial Port Process name here",
    UseShellExecute = false,
    RedirectStandardOutput = true,
};

using(var process = Process.Start(options))
{
    using (StreamReader reader = process.StandardOutput)
    {
        while (!process.HasExited)
        {
            string result = reader.ReadLine();
            Console.WriteLine(result);
        }
    }
}
Share:
14,845
26071986
Author by

26071986

Updated on July 22, 2022

Comments

  • 26071986
    26071986 almost 2 years

    I'm working with SerialPort to communicate (read only) with barcode reader.

    I've installed driver to operate with the reader as if it was connected via Com-port, though it is a usb device. When the device is plugged, one more Com-port appears in the list.

    The problem is the following. I initialize the SerialPort object to read from barcode reader, but if the reader is unplugged, I have no way to finalize or dispose the SerialPort object correctly, because the port it is "attached" to no longer exists.

    The result is WinIOException when the program is closed. I cannot catch it not only in the code working with the SerialPort but at the program.cs level as well. According to the stack WinIOException is thrown after the attempts of finalizing and disposing the SerialPort object.

    Are there any ideas how I can operate with this situation properly? Or at least to catch the exception?

    The thing I know for sure is that the problem is not in this particular driver; I had one more barcode reader from another manufacturer (with the same purpose driver) - the situation is the same.

  • 26071986
    26071986 almost 14 years
    Thank you, I'll try as soon as I can.
  • dbasnett
    dbasnett almost 14 years
    did they really fix this in 4.0? One day soon I should test it for myself. If so, then all I can say is about time. The bug has been around for a very long time.
  • 26071986
    26071986 almost 14 years
    Hans Passant .Net 3.5. Checked it in a simple test application. Tomorrow will check in a main application.
  • Panagiotis Kanavos
    Panagiotis Kanavos almost 14 years
    Just checked SerialPort.Dispose with Reflector. The reason it throws it that it calls Close on its internal SerialStream class, which in turn makes an unsafe call to EscapeCommFunction, throwing an IOException if this fails.
  • user1703401
    user1703401 almost 14 years
    Ah, the world makes sense again. You really ought to change the answer mark.
  • Tony Vitabile
    Tony Vitabile almost 12 years
    I've used this answer and it worked for me. I was able to re-open the port once the device was plugged back in, too. What I did was call Dispose after calling Close, then set the variable to null. My code then keeps trying to re-establish contact. Each time, it allocates a new SafeSerialPort object (what I called the class that descends from SerialPort) and tries to connect. If it fails, it calls Close, Dispose, and sets the variable back to null again. Works fine. However, the device & it's driver you're using may behave differently.
  • Michael Graczyk
    Michael Graczyk over 11 years
    @Chimpster I ran into this problem recently and tried your workaround. It worked sometimes, and other times I actually crashed with a blue screen (Bug Check). I gave up and just decided to deal with my application crashing when the serial cable is unplugged. Sometimes it isn't worth working around with other people's bad code. (USB to COM adapter driver)
  • Richard J Foster
    Richard J Foster almost 10 years
    No, it's not fixed in 4.0 (or 4.5 come to that), you're just able to stop the exception from terminating the application by "ignoring" it (along with any other similar exceptions from other sources).