1

I have a Visual Studio C++ 2013 MFC application that writes/reads data from a serial rotary encoder, and sends the read data to a stepper motor. The reading is in a while (flagTrue) {}. The while loop is spooled in a separate thread.

It does the job, but when I try to exit the application graciously,I keep getting this:

'System.ObjectDisposedException' mscorlib.dll

I tried setting timers for 1-2 seconds to let the serial listening finish, but it seems like the listening keeps going even when I seemingly have exited the thread. Here are snippets of the code:

//this is inside of the main window CDlg class
pSerialThread = (CSerialThread*)AfxBeginThread(RUNTIME_CLASS(CSerialThread)); 

//this is inside the CSerialThread::InitInstance() function
init_serial_port();

//this is the serial listening while loop
void init_serial_port() {
SerialPort^ serialPort = gcnew SerialPort();
while (bSerialListen) {
//do read/write using the serialPort object
}
serialPort->Close();
}

///this is in the OnOK() function
bSerialListen = false;
pSerialThread->ExitInstance();

An incomplete workaround, inspired Hans's answer below, was to have the thread reset a flag after port closes:

SerialPort^ serialPort = gcnew SerialPort();
serialIsOpen = true;
while (bSerialListen) {
//do read/write using the serialPort object
}
serialPort->Close();
serialIsOpen = false;
}

Then inside OnOK() (which does result in a clean exit):

bSerialListen=false;
//do other stuff which normally takes longer than the port closing.
if (serialIsOpen) {
 //ask user to press Exit again;
return;
}
OnOK();
}

However, the user always has to press Exit twice, because the following never works

while (serialIsOpen) {
    Sleep(100);
    //safety counter, do not proceed to OnOK();
    }
OnOK();

while expires before the port resets the flag, even if one waits for 10 seconds -- much longer than the user pressing the button twice:

  • It looks like the managed `SerialPort` class is being accessed while/after it has been disposed. What language are you using for managed/unmanaged interop? – IInspectable Oct 29 '15 at 21:10
  • Sorry -- I have a UK keyboard on a US PC...Visual C++, using using namespace System; using namespace System::IO::Ports; – runcyclexcski Oct 29 '15 at 21:25
  • Well, then, what language are you using? C++/CLI? Managed C++? Something else? – IInspectable Oct 29 '15 at 21:27
  • Sorry if my answers are dumb, I presumed that when I wrote above that it's VC++ in visual studio 2013, it was enough information. I guess I was wrong. In Project Properties_C/C++/General, I had to enable /clr to make the serial port objects recognizable, that's all I know :). – runcyclexcski Oct 29 '15 at 21:41
  • Can you set Visual Studio to break on `ObjectDisposedException`, and look at the stack trace? That should tell you exactly what object is being accessed after it was disposed, and from where. – David Yaw Oct 29 '15 at 21:59
  • I tried to trigger closure of the serialPort by creating a handle to it in other classes, or the global space. But as soon as I try to create a handle of SerialPort^ type anywhere but in a local function (shown above), I get an error saying that I cannot mix managed and unmanaged code in classes, or in global space. That is why the only way to tell the port to close was to create bSerialListen in the global space, set it to false, and wait until it is seen by the loop. This works with mixed success **System.ObjectDisposedException occurred in mscorlib.dll ** – runcyclexcski Oct 30 '15 at 00:36

1 Answers1

2
 while (bSerialListen) {

Very troublesome. First of all, a bool is not a proper thread synchronization primitive by a very long shot. Second of all, surely the most likely problem, is that your code isn't checking it. Because the thread is actually stuck in the SerialPort::Read() call. Which isn't completing because the device isn't sending anything at the moment you want to terminate your program.

What happens next is very rarely graceful. A good way to trigger an uncatchable ObjectDisposedException is to jerk the USB connector. The only other thing you can do when you see it not working and have no idea what to do next. Very Bad Idea. That makes many a USB driver throw up its hands in disgust, it knows a userland app has the port opened but it isn't there anymore and now starts failing any requests. Sometimes even failing the close request, very unpleasant. There is no way to do this in a graceful way, serial port are not plug & play devices. This trips ODE in a worker thread that SerialPort starts to raise events, it is uncatchable.

Never, never, never jerk the USB connector, using the "Safely Remove Hardware" tray icon is a rock-hard requirement for legacy devices like serial ports. Don't force it.

So what to do next? The only graceful way to get the SerialPort::Read() call to complete is to jerk the floor mat. You have to call SerialPort::Close(). You still get an ObjectDisposedException but now it is one you can actually catch. Immediately get out of the loop, don't do anything else and let the thread terminate.

But of course you have to do so from another thread since this one is stuck. Plenty of trouble doing that no doubt when you use MFC, the thread that wants it to exit is not a managed thread in your program. Sounds like you already discovered that.

The better way is the one you might find acceptable after you read this post. Just don't.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Thanks, Hans. If it helps at all, I do put the while loop into Sleep(5), like inan example that came with the encoder. The encoder reads/writes data at 100Hz (measured using time stamps at every iteration of the while loop). So I presumed that if I set the global flag bSerialListen to false the thread would see it in 0.01 seconds, and carry on to SerialPort::Close() outside of the while {} loop. But it does not seem to work. I can catch the ObjectDisposedException, but, again, the only way to get out of the loop is to set the bSerialListen to false, so I am back where I started. – runcyclexcski Oct 30 '15 at 23:09
  • I already told you that using a *bool* is not correct, a ManualResetEvent is for example. You are not using my advice, good luck with it. – Hans Passant Oct 30 '15 at 23:44
  • OK, I guess I can only use what I can understand at the moment. After all, only 1 sentence was dedicated to bool being, with the rest dedicated to how important it is to exit gracefully. So, for now, I have a student working on her microscope w/o seeing error messages. – runcyclexcski Oct 31 '15 at 00:27