Link to home
Start Free TrialLog in
Avatar of ThePlague1347
ThePlague1347Flag for United States of America

asked on

C# SerialPort Crashing App After Close

Greetings Experts! I am facing a dilemma and finding myself scratching the remaining few bits of hair off my head. I connect to a SerialPort to pull weight data from a scale (industrial size). The data is received correctly and all information is updated appropriatley. The scale remains connected for the life of the application until exit. Upon exit, I can verify the connection to the scale and the thread controlling the scale connection are closed. Immediatly after that, I receive an App Crash error from Windows. If I remove the scale connection, the window will close just fine. This is difficult to debug because I am having to test at my customers location remotely. I have attached the code for the scale thread and the exiting code (Form_Closing event) .
protected ManualResetEvent threadStop = new ManualResetEvent(false);

//Init the scale thread
            if (scaleThread == null)
            {
                scaleThread = new Thread(new ThreadStart(InitScaleThread));
                scaleThread.IsBackground = true;
                scaleThread.Start();
            }

private void InitScaleThread()
{
    
    // --Code removed, just updating buttons and labels and pulling data into DataSet
    
    if (CommonMembers.IsValidDataSet(oDS))
    {
	DataRow oDR = oDS.Tables[0].Rows[0];

	using (scalePort = new SerialPort(oDR["Port"].ToString()))
	{
	    try
	    {
		scalePort.Parity = (System.IO.Ports.Parity)Enum.Parse(typeof(System.IO.Ports.Parity), oDR["Parity"].ToString());
		scalePort.BaudRate = Convert.ToInt32(oDR["BaudRate"].ToString());
		scalePort.StopBits = (System.IO.Ports.StopBits)Enum.Parse(typeof(System.IO.Ports.StopBits), oDR["StopBits"].ToString());
		scalePort.DataBits = Convert.ToInt32(oDR["DataBits"].ToString());
		scalePort.Handshake = Handshake.XOnXOff;
		scalePort.ReadTimeout = 2000;

		scalePort.Open();

		string data, prevdata;
		prevdata = "";
		while (scalePort.IsOpen)
		{
		    data = scalePort.ReadLine();

		    if ((data.Length > 0) && prevdata != data)
		    {
			prevdata = data;
			scalePort_DataReceived(scalePort, data);
		    }

		    Thread.Sleep(10);

		    if (threadStop.WaitOne(0))
		    {
		    	// Added the below as a test, although it shouldn't hurt anything
			scalePort.DtrEnable = false;
			scalePort.RtsEnable = false;
			scalePort.Close();
			break;
		    }
		}
	    }
	    catch (TimeoutException)
	    {
		// Do Nothing
	    }
	    catch (InvalidOperationException)
	    {
	    	// Added to try and capture error if any for testing only
	    	CommonMembers.SendSupportEmail();		
	    }
	    catch (UnauthorizedAccessException)
	    {
	    	// Added to try and capture error if any for testing only
	    	CommonMembers.SendSupportEmail();
	    }
	    catch (IOException)
	    {
	    	// Added to try and capture error if any for testing only
		CommonMembers.SendSupportEmail();
	    }
	}     
    }
    else
    {
	// -- Removed Code updating buttons and label, etc...
    }

}

Open in new window

void scalePort_DataReceived(object sender, String DataRead)
{
    try
    {
	string value = "";
	float scaleValue = 0;

	char[] cData = DataRead.ToCharArray();

	// Break the data apart and find the numbers - Should probably be done with Regex
	for (int i = 0; i < cData.Length; i++)
	{
	    if (CommonMembers.IsNumeric(Convert.ToString(cData[i])))
		value += Convert.ToString(cData[i]);
	    else if (Convert.ToString(cData[i]) == "L" || Convert.ToString(cData[i]) == "T")
		break;
	    Thread.Sleep(1);
	}

	if (CommonMembers.IsFloat(value))
	{
	    scaleValue = float.Parse(value);
		
	    // Update PIN Location and gauge max values
	    if (scaleValue < 500)
		SetScaleMaxValue(500);
	    else if (scaleValue > arcScale.MaxValue)
		SetScaleMaxValue(scaleValue + 50);
	    else if (scaleValue < 0)
		SetScaleValue(0);
	    else
	    {
		SetScaleValue(scaleValue);
		SetScaleVisibleValue(scaleValue.ToString());
	    }
	}
    }
    catch { }
}

Open in new window

// In Form_Closing Event
stopThread.Set();

if (scaleThread != null)
                {
// Added for testing
                    scaleThread.Abort();
                    // Added for testing
                    while (scaleThread.ThreadState != ThreadState.Stopped)
                    {
                        Thread.Sleep(500);
                    }
                }
                scaleThread = null;

Open in new window

SOLUTION
Avatar of Chuck Yetter
Chuck Yetter
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of ThePlague1347

ASKER

Thank you for your response Axshun! I tried your implementation, the application no longer crashes. Instead it is hanging now (freezing) so something is still holding onto the port waiting for it to close.

I also added Application.ExitThread(); to the Form_Closing. This is the Form_Closing on the Main form so I hope that is not duplicating Form_Closing calls on exit.
ASKER CERTIFIED SOLUTION
Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Thank you for your feedback Idle_Mind. I implemented your suggestion and it still did not work.

You are correct on the Close() method. However, I was not clear on the version of .NET I was using.  In 3.5 the App went back to crashing. I recompiled to 4, the app still hangs on close.
Do you still have the ManualResetEvent in there?...don't think that is necessary.
No I commented it out...it wouldn't have hurt cause we are checking for null on the scalePort but just to be sure I did.
It appears my background thread is not closing. After running a few other tests the port appears to be closing the application closing is not terminating the thread. I have tried scaleThread.Join() and scaleThread.Abort() (I know the Abort "usually" closes and is not good, but just for giggles). Any other suggestions?
That's why I said you probably don't need the ManualResetEvent in there...

Do you really need to explicitly close the serial port anyways?  You've placed it into a "using" block which means it will automatically be disposed of.  Also, since you set IsBackground() to true, the thread should be killed automatically when the app exits.
ManualResetEvent is no longer used.
The port is closing...the thread is not.
The thread IsBackground is set to true...thats the confusing part. Application.ExitThread waits for all threads to close right?...the AppCrash happens shortly after joing the thread (it appears).
Where and why are you Join()ing which thread on exit?...that should not be necessary either.
I'm trying to test different stuff just to get it to work, LOL
Joining the thread should add it to the applications main thread so it doesn't have to wait for it. I even added a 5 second delay to allow the thread to close. No beans. Gotta double check that though, I'm using 4.0 now. I can check ThreadState which hangs the app.
Could you explain your first statement more...it doesn't make sense to me.

From my understanding ( and please correct me as I am pulling from the top of my head ). The Thread.Join should add the background thread to main thread (the application). When the application closes, it no longer waiting for that backgroung thread to close because it is now a part of the main thread.

On a side note...

I added a while loop to check for ThreadState. The ThreadState.Aborted is shown is and the Form_Closing continues....with a crash. Cameras, etc are closed first...e.cancel = false; is the last/next line to be called. If I "erase" all scale connections the applications closes like it should. This is driving me nuts.
The Join() method tells the current thread to STOP until the thread being joined has completed:
http://msdn.microsoft.com/en-us/library/95hbf2ta.aspx

    "Blocks the calling thread until a thread terminates, while continuing to perform standard COM and SendMessage pumping."

For example, assume the current thread is "A", and we Join() with thread "B":

    // ... currently running in threadA ...
    threadB.Join(); // <-- threadA STOPS here until threadB EXITS
    // ... threadA continues running ...

So this could theoretically keep your application from exiting if threadB never exits properly.  You could be ending up in a race condition where the main thread is waiting for the secondary thread to exit but the secondary thread cannot exit because some condition or ManualResetEvent has not been set and/or processed (since the main app message pump is NOT processing while Join()ed).
So, Thread.Join(), we can rule out. Thread "B" has exited and thread "A" (main thread) crashes. (Again, ManualResetEvent is not being used, deleted/erased.  It was doing that before I added the ManualResetEvent).
Alright, well. After reviewing the crashdump logs, it turns out after all this it was the DVR camera system not shutting down correctly. Although I think we did resolve some issues with the scale connection(s) at the same time. Thanks for all your help!
Glad you figured it out!...as given the root cause, we probably never would have guessed that from our end.  =)