Question

I'm writing a Windows Service for communication with a Serial Mag-stripe reader and a relay board (access control system).

I run into problems where the code stops working (i get IOExceptions) after another program has "interrupted" the process by opening the same serial port as my service.

Part of the code is as follows:

public partial class Service : ServiceBase
{
    Thread threadDoorOpener;
    public Service()
    {
        threadDoorOpener = new Thread(DoorOpener);
    }
    public void DoorOpener()
    {
        while (true)
        {
            SerialPort serialPort = new SerialPort();
            Thread.Sleep(1000);
            string[] ports = SerialPort.GetPortNames();
            serialPort.PortName = "COM1";
            serialPort.BaudRate = 9600;
            serialPort.DataBits = 8;
            serialPort.StopBits = StopBits.One;
            serialPort.Parity = Parity.None;
            if (serialPort.IsOpen) serialPort.Close();
            serialPort.Open();
            serialPort.DtrEnable = true;
            Thread.Sleep(1000);
            serialPort.Close();
        }
    }
    public void DoStart()
    {
        threadDoorOpener.Start();
    }
    public void DoStop()
    {
        threadDoorOpener.Abort();
    }
    protected override void OnStart(string[] args)
    {
        DoStart();
    }
    protected override void OnStop()
    {
        DoStop();
    }
}

My sample program successfully starts the work-thread, and the opening/closing and raising of DTR causes my Mag-stripe reader to power up (wait 1sec), shut down (wait 1 sec) and so on.

If I launch HyperTerminal and connects to the same COM port, HyperTerminal tells me the port is currently in use. If i repeatedly press ENTER in HyperTerminal, to try to reopen the port it will succeed after a few retries.

This has the effect of causing IOExceptions in my work-thread, which is expected. However, even if I close down HyperTerminal, i still get the same IOException in my work-thread. The only cure is actually to restart the computer.

Other programs (which is not using .NET libraries for port-access) seem to work normally at this point.

Any ideas as to what is causing this?

Was it helpful?

Solution

@thomask

Yes, Hyperterminal does in fact enable fAbortOnError in SetCommState's DCB, which explains for most of the IOExceptions thrown by the SerialPort object. Some PCs / handhelds also have UARTs that have the abort on error flag turned on by default - so it's imperative that a serial port's init routine clears it (which Microsoft neglected to do). I wrote a long article recently to explain this in greater detail (see this if you're interested).

OTHER TIPS

You can't close someone elses connection to a port, the following code will never work:

if (serialPort.IsOpen) serialPort.Close();

Because your object didn't open the port you can't close it.

Also you should close and dispose the serial port even after exceptions occur

try
{
   //do serial port stuff
}
finally
{
   if(serialPort != null)
   {
      if(serialPort.IsOpen)
      {
         serialPort.Close();
      }
      serialPort.Dispose();
   }
}

If you want the process to be interruptible then you should Check if the port is open and then back off for a period and then try again, something like.

while(serialPort.IsOpen)
{
   Thread.Sleep(200);
}

Have you tried leaving the port open in your application, and just turning DtrEnable on/off, and then closing the port when your application closes? i.e:

using (SerialPort serialPort = new SerialPort("COM1", 9600))
{
    serialPort.Open();
    while (true)
    {
        Thread.Sleep(1000);
        serialPort.DtrEnable = true;
        Thread.Sleep(1000);
        serialPort.DtrEnable = false;
    }
    serialPort.Close();
}

I'm not familiar with DTR semantics, so I don't know if this would work.

How to do reliable async comms

Don't use the blocking methods, the internal helper class has some subtle bugs.

Use APM with a session state class, instances of which manage a buffer and buffer cursor shared across calls, and a callback implementation that wraps EndRead in a try...catch. In normal operation, the last thing the try block should do is set up the next overlapped I/O callback with a call to BeginRead().

When things go awry, catch should asynchronously invoke a delegate to a restart method. The callback implementation should exit immediately after the catch block so that the restart logic can destroy the current session (session state is almost certainly corrupt) and create a new session. The restart method must not be implemented on the session state class because this would prevent it from destroying and recreating the session.

When the SerialPort object is closed (which will happen when the application exits) there may well be a pending I/O operation. When this is so, closing the SerialPort will trigger the callback, and under these conditions EndRead will throw an exception that is indistinguishable from a general comms shitfit. You should set a flag in your session state to inhibit the restart behaviour in the catch block. This will stop your restart method from interfering with natural shutdown.

This architecture can be relied upon not to hold onto the SerialPort object unexpectedly.

The restart method manages the closing and re-opening of the serial port object. After you call Close() on the SerialPort object, call Thread.Sleep(5) to give it a chance to let go. It is possible for something else to grab the port, so be ready to deal with this while re-opening it.

I think I have come to the conclusion that HyperTerminal does not play well. I've run the following test:

  1. Start my service in "console mode", it starts switching the device on/off (i can tell by it's LED).

  2. Start HyperTerminal and connect to the port. The device stays on (HyperTerminal raises DTR) My service writes to the event log, that it cannot open the port

  3. Stop HyperTerminal, I verify it is properly closed using task manager

  4. The device stays off (HyperTerminal has lowered DTR), my app keeps on writing to the event log, saying it cannot open the port.

  5. I start a third application (the one I need to coexist with), and tell it to connect to the port. I does so. No errors here.

  6. I stop the above mentioned application.

  7. VOILA, my service kicks in again, the port opens successfully, and the LED goes ON/OFF.

I've tried changing the work-thread like this, with the exact same result. Once HyperTerminal once succeeds in "capturing the port" (while my thread is sleeping), my service won't be able to open the port again.

public void DoorOpener()
{
    while (true)
    {
        SerialPort serialPort = new SerialPort();
        Thread.Sleep(1000);
        serialPort.PortName = "COM1";
        serialPort.BaudRate = 9600;
        serialPort.DataBits = 8;
        serialPort.StopBits = StopBits.One;
        serialPort.Parity = Parity.None;
        try
        {
            serialPort.Open();
        }
        catch
        {
        }
        if (serialPort.IsOpen)
        {
            serialPort.DtrEnable = true;
            Thread.Sleep(1000);
            serialPort.Close();
        }
        serialPort.Dispose();
    }
}

This code seems to work properly. I've tested it on my local machine in a console application, using Procomm Plus to open/close the port, and the program keeps on ticking.

    using (SerialPort port = new SerialPort("COM1", 9600))
    {
        while (true)
        {
            Thread.Sleep(1000);
            try
            {
                Console.Write("Open...");
                port.Open();
                port.DtrEnable = true;
                Thread.Sleep(1000);
                port.Close();
                Console.WriteLine("Close");
            }
            catch
            {
                Console.WriteLine("Error opening serial port");
            }
            finally
            {
                if (port.IsOpen)
                    port.Close();
            }
        }
    }

This answer got to long to be a comment...

I believe that when your program is in a Thread.Sleep(1000) and you open your HyperTerminal connection, the HyperTerminal takes control over the serial port. When your program then wakes up and trying to open the serial port, an IOException is thrown.

Redesign your method and try to handle the opening of the port in a different way.

EDIT: About that you have to reboot your computer when your program fails...

That probably because your program isn´t really closed, open your taskmanager and see if you can find your program service. Be sure to stop all your threads before exiting your application.

Is there a good reason to keep your service from "owning" the port? Look at the built-in UPS service -- once you tell it there's an UPS attached to, say, COM1, you can kiss that port goodbye. I'd suggest you do the same unless there's a strong operational requirement to share the port.

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