locked
serial port hangs whilst closing

    Question

  • i'm having an issue where my form sometimes hangs on closing (when it closes the serial port).
    I've tried it lots of different ways, and tried adding a sleep after closing the port before closing the form, but it is still failing occasionally.

     

    at the moment, i catch someone closing the form, then close the serial port before the form then closes like this: 

    Code Snippet

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)

    {

    if (serialPort1.IsOpen)

    {

    e.Cancel = true; //cancel the fom closing

    serialPort1.Close(); //close the serial port

    this.Close(); //now close the form

    }

     

    when i run the program, and when it hangs when i close it, If i press "pause" on the debugger there is a green arrow next to the "serialPort1.Close()" line, if I hover over that it states "The code has called into another function. When that function is finished this is the next statement that will be executed"

     

    the call stack is like this:

    Code Snippet
      [In a sleep, wait, or join] 
      System.dll!System.IO.Ports.SerialStream.Dispose(bool disposing = true) + 0x154 bytes 
      mscorlib.dll!System.IO.Stream.Close() + 0x10 bytes 
      System.dll!System.IO.Ports.SerialPort.Dispose(bool disposing = true) + 0x3c bytes 
      System.dll!System.IO.Ports.SerialPort.Close() + 0xa bytes 
    > GPSspeed.exe!GPSspeed.Form1.Form1_FormClosing(object sender = {GPSspeed.Form1}, System.Windows.Forms.FormClosingEventArgs e = {System.Windows.Forms.FormClosingEventArgs}) Line 560 + 0x10 bytes C#
      System.Windows.Forms.dll!System.Windows.Forms.Form.OnFormClosing(System.Windows.Forms.FormClosingEventArgs e) + 0x62 bytes 
      System.Windows.Forms.dll!System.Windows.Forms.Form.WmClose(ref System.Windows.Forms.Message m = {System.Windows.Forms.Message}) + 0xb6 bytes 

     

    does this suggest it hung closing the serial port, or it's actually hung trying to the first part of closing the form?

     

     

     

    Saturday, April 28, 2007 3:27 PM

Answers

  • This is a known issue with the SerialPort class and described in this Product Feedback article as well as several threads in these forums.  You may notice the "closed by design" dismissal.  If you think that hanging programs by design is poor design, feel free to add your comment/vote. 

    One known way to get this behavior is to quickly close, then re-open the port.  The next close hangs.  Successful workarounds that have been reported are closing the port in another thread (odd one) and not closing the port.  One thing I'd try is turning off the hardware handshake signals (RtsEnable and DtrEnable = False), then sleeping for a second or so to let any pending DataReceived events drain away.
    Sunday, April 29, 2007 8:26 PM
    Moderator

All replies

  • This is a known issue with the SerialPort class and described in this Product Feedback article as well as several threads in these forums.  You may notice the "closed by design" dismissal.  If you think that hanging programs by design is poor design, feel free to add your comment/vote. 

    One known way to get this behavior is to quickly close, then re-open the port.  The next close hangs.  Successful workarounds that have been reported are closing the port in another thread (odd one) and not closing the port.  One thing I'd try is turning off the hardware handshake signals (RtsEnable and DtrEnable = False), then sleeping for a second or so to let any pending DataReceived events drain away.
    Sunday, April 29, 2007 8:26 PM
    Moderator
  • thanks dude!! - i owe you a pint.

     

    seems to work perfectly closing in another thread..

     

    Code Snippet

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)

    {

    if (serialPort1.IsOpen)

    {

    e.Cancel = true; //cancel the fom closing

    Thread CloseDown = new Thread(new ThreadStart(CloseSerialOnExit)); //close port in new thread to avoid hang

    CloseDown.Start(); //close port in new thread to avoid hang

    }

    }

    private void CloseSerialOnExit()

    {

    try

    {

    serialPort1.Close(); //close the serial port

    }

    catch (Exception ex)

    {

    MessageBox.Show(ex.Message); //catch any serial port closing error messages

    }

    this.Invoke(new EventHandler(NowClose)); //now close back in the main thread

    }

    private void NowClose(object sender, EventArgs e)

    {

    this.Close(); //now close the form

    }

     

    Sunday, April 29, 2007 9:05 PM
  • I think I need some stronger than beer (h3mp perhaps) to make sense of this.  But it has been reported as a fix before.  Please let us know in a month or so if this continues to be a solution.  I fear the delay caused by invoking the Close() method is the real reason this works.  Kindly check by just calling System.Threading.Thread.Sleep(1000) before calling Close().
    Sunday, April 29, 2007 9:56 PM
    Moderator
  • i had tried Thread.Sleep(1000); and Thread.Sleep(5000) before this.close() in the past, but occasionally it still failed, from the stack, you can see the failure is before the thread.sleep.... i've been testing non-stop (might need a new F5 key), and the separate thread approach appears to be the fix - if it fails, i'll definitely reply...
    Sunday, April 29, 2007 10:24 PM
  • Thanks for the feedback.  It shatters one more assumption what is wrong with the darned thing.  Kim Hamilton: please feel free to comment.
    Sunday, April 29, 2007 10:53 PM
    Moderator
  • It appears this problem applies to VC++ 2005 too.

     

    Also, I'm confused. The Issue Status says "Closed (By Design)".

     

    If SerialPort->Close() is "Designed" to work this way then it should be renamed to:

     

    SerialPort->HangApp(Intermittency=>50%)

     

    It would have saved me days of debugging if it were named more appropriately.

     

    Seriously though, it seems like MS has just ignored Serial Port devices over the past 10 years in the hopes that they would just go away. They even convinced PC manufacturers to stop including SerialPorts on some PCs (PC99 Spec?).  Why? Because RS-232 doesn't support PnP.

     

    But despite this decade long effort to kill the serial port, there are more RS-232 devices out there now then ever before.

     

    The one thing MS can't seem to fathom...

     

    RS-232 is still around not because it is End User Friendly. But, because it is Hardware Friendly (less transistors to make a serial interface then a USB interface = lower cost implementation) and "theoritically" Programmer Friendly (no endpoints, enumeration and descriptors to worry about. Just open the port and read/write).

     

    So they made an attempt to include serial port functionality in .Net but it doesn't seem like they are serious about making the SerialPort Object work (much less document it).

     

    Obviously I have some "issues" here. I am new to programming and thought I would start by developing some serial port based apps just to get my feet wet. Boy, did I pick the wrong "Hello World" app.

     

    Now, if I wanted to write a Database App, I could get all the documentation and samples I want. But if I want to read from a Serial Port (with VC++ .Net), based on the available documentation, you would think the SerialPort Object was classified "Top-Secret" in Managed C.

     

    Ok, I'm done with my rant...

     

    So how do we get this Issue reopened so the SerialPort.Close() [or SerialPort->Close()]  function gets fixed?


     

    Monday, May 07, 2007 11:28 PM
  • I'll try to get a hold of Kim Hamilton.
    Tuesday, May 08, 2007 1:08 AM
    Moderator
  • I don't suppose anybody knows how to port the "close SerialPort with a new thread" workaround from C# to VC++?

    I've been working on it for a couple days and this is a far as I have gotten.

    Code Snippet


    private: System::Void Form1_FormClosing(System::Object^  sender, System::Windows::Forms::FormClosingEventArgs^  e)
             {
    if (serialPort1->IsOpen)
    {
    e->Cancel = true; //cancel the form closing
    Thread^ CloseDown = gcnew Thread(gcnew ThreadStart(&GPSReaderinC::Form1::CloseSerialOnExit)); //close port in new thread to avoid hang
    CloseDown->Start(); //close port in new thread to avoid hang
    }
    }
    private: void CloseSerialOnExit()
    {
    try
    {
    serialPort1->Close(); //close the serial port
    }
    catch (Exception ex)
    {
    MessageBox->Show(ex.Message); //catch any serial port closing error messages
    }
    this->Invoke(gcnew EventHandler( &GPSReaderinC::Form1::NowClose)); //now close back in the main thread
    }
    public: void NowClose(Object^ sender, System::EventArgs^ e)
    {
    this->Close(); //now close the form
                 
            }


    It keeps telling me that:

    Thread^ CloseDown = gcnew Thread(gcnew ThreadStart(&GPSReaderinC::Form1::CloseSerialOnExit));

    "Error    1    error C3350: 'System::Threading::ThreadStart' : a delegate constructor expects 2 argument(s)    c:\source files\c++ application\gps reader in c\gps reader in c\gps reader in c\Form1.h    473"

    and

    this->Invoke(gcnew EventHandler( &GPSReaderinC::Form1::NowClose));

    "Error    6    error C3352: 'void GPSReaderinC::Form1::NowClose(System:Surprisebject ^,System::EventArgs ^)' : the specified function does not match the delegate type 'void (System:Surprisebject ^,System::EventArgs ^)'    c:\source files\c++ application\gps reader in c\gps reader in c\gps reader in c\Form1.h    487"

    Also, I'm getting syntax errors with the catch exception and messagebox but I think I can figure that out with some examples.
    Wednesday, May 09, 2007 2:47 PM
  • if you are using the .net version of VC++ (the express edition),

    if it's like C#, you should have a "BackgroundWorker" in your toolbox - add this to your form, double click it and add "serialPort1->Close()" to it's do_work method,
    then from your main program call "backgroundWorker->RunWorkerAsync();"

    ??
    Wednesday, May 09, 2007 3:10 PM
  • Try this:

    Code Snippet

      private:
        System::Void Form1_FormClosing(System::Object^  sender, System::Windows::Forms::FormClosingEventArgs^  e) {
          if (serialPort1->IsOpen) {
            e->Cancel = true;
            Thread^ CloseDown = gcnew Thread(gcnew ThreadStart(this, &Form1::CloseSerialOnExit));
            CloseDown->Start();
          }
        }
        void CloseSerialOnExit() {
          try {
            serialPort1->Close();
            this->Invoke(gcnew MethodInvoker(this, &Form1::NowClose));
          }
          catch (Exception^ ex) {
            MessageBox::Show(ex->Message);
          }
        }
        void NowClose() {
          this->Close();
        }



    Wednesday, May 09, 2007 3:18 PM
    Moderator
  • Actually, I am using VS2005 VC++ (Professional Edition) but I hope that doesn't make a difference.

    Anyway, I added a BackgroundWorker to my form, double clicked and added the SerialPort->Close(); as you suggested. I still got errors from the other form_closing code so I removed it (was that the right thing to do) and replaced with the following:

    Code Snippet

    private: System::Void Form1_FormClosing(System::Object^  sender, System::Windows::Forms::FormClosingEventArgs^  e)
             {

    if (serialPort1->IsOpen)
    {
    e->Cancel = true; //cancel the form closing

    backgroundWorker1->RunWorkerAsync();

    this->Close(); //now close the form
    }
                 
            }


    private: System::Void backgroundWorker1_DoWork(System::Object^  sender, System::ComponentModel::DoWorkEventArgs^  e)
             {
                 serialPort1->Close();
             }



    Now it runs but whenever I try to close the app, I get the following exception:

    "This BackgroundWorker is currently busy and cannot run multiple tasks concurrently."

    I feel like I'm flying blind, I wrote this app to open a serialport, read from a GPS receiver and update labels on the form but getting it close without hanging is proving to be the most complicated part of the app.

    Thanks for helping me.
    Wednesday, May 09, 2007 3:49 PM
  •  nobugz wrote:
    Try this:

    Code Snippet

    private:
    System::Void Form1_FormClosing(System::Object^ sender, System::Windows::Forms::FormClosingEventArgs^ e) {
    if (serialPort1->IsOpen) {
    e->Cancel = true;
    Thread^ CloseDown = gcnew Thread(gcnew ThreadStart(this, &Form1::CloseSerialOnExit));
    CloseDown->Start();
    }
    }
    void CloseSerialOnExit() {
    try {
    serialPort1->Close();
    this->Invoke(gcnew MethodInvoker(this, &Form1::NowClose));
    }
    catch (Exception^ ex) {
    MessageBox::Show(ex->Message);
    }
    }
    void NowClose() {
    this->Close();
    }





    Yeaaaa!!!

    It works!!!

    Thanks nobugz

    I'll let everyone know if I get anymore hangs on closing.


    Wednesday, May 09, 2007 3:55 PM
  • I got in touch with Kim Hamilton about this.  Thank you Ed.  As it turns out, this is a well known problem, just not documented in the Product Feedback article.  In a nutshell, this happens when you use Control.Invoke() in the DataReceived event.  Which would be a very common thing to do.  The workarounds are to use Control.BeginInvoke() instead or, indeed the workaround discussed in this thread, to call Close() on another thread.  Check out tip #3 in this blog post.  I've left a comment on the Product Feedback article as well.

    Thank you Kim.
    Friday, May 11, 2007 9:33 PM
    Moderator
  • Thanks for posting the link to the workaround Nobugz!

     

    And to everyone in this thread -- we're very sorry that the workaround wasn't included when it was resolved as "By Design". It looks like this serial port deadlock problem has been tripping people up, both on the forums and connect. I'm glad Nobugz brought this to my attention -- our team is now taking steps to make sure that doesn't happen again.

     

    About the workaround possibilities -- I prefer the Control.BeginInvoke workaround because it's less invasive and because it's a good idea to sychronously handle only serial port-related* actions in your event handler, and do anything else, such as updating the GUI, in a separate thread (BeginInvoke achieves exactly that). The calls to your event handler are queued up, so putting too much work in there will slow down how often you can be notified of additional events.

     

    *This is a vague term, but the idea is that, if you get a data received event, then in most cases it's more straightforward to fetch the new data synchronously. Nobugz mentioned a question about this has come up on another thread. In case you're interested in these details, I'll post a link to that thread after I've posted.

     

    Thanks,

    Kim

    Saturday, May 12, 2007 1:03 AM
  • Saturday, May 12, 2007 6:10 AM
  • Hi all!

     

    Sorry to bring up this issue again but I'm having similar problems with calling SerialPort.Close in a Compact Framework app. Unfortunately none of the workarounds discussed here work.

     

    I'm using the SerialPort class to connect a bluetooth barcode scanner to a symbol MC3090 mobile device based on Windows CE 5.0. The code looks like this:

     

    Code Snippet

    Public Sub [Stop]() Implements Kaufhof.Allgemein.Plugin.IPlugin.Stop

        Dim t As Threading.Thread = New Threading.Thread(AddressOf ClosePort)

        t.Start()

    End Sub

     

    Private Sub ClosePort()

        If mSerialPort.IsOpen Then

            mSerialPort.DiscardInBuffer()

            mSerialPort.Close()

            mSerialPort.Dispose()

        End If

    End Sub

     

    Private Sub SerialPort_DataReceived(ByVal sender As Object, ByVal e As SerialDataReceivedEventArgs) Handles mSerialPort.DataReceived

        While mSerialPort.BytesToRead > 0

            Dim data As String = mSerialPort.ReadLine()

            myControl.Invoke(New ProcessDataDelegate(AddressOf ProcessData), New Object() {data})

        End While

    End Sub

     

    Private Sub ProcessData(ByVal data As String)

        Dim BCEventArgs As New BarcodeEventArgs(data, BarcodeTypes.NOTSUPPORTED, mSerialPort.PortName)

        RaiseEvent ReadNotify(Me, BCEventArgs)

    End Sub

     

    I wrote a simple test app with two buttons (one to start the scanner, the other to stop it) to see everything works. While this runs without problems on a HP 2410 it hangs at SerialPort.Close() on a Symbol MC3090. As you can see I already tried calling close on a separate thread but nevertheless the app hangs until I disable bluetooth on the device which shuts down the serial port.

     

    I don't know how the call to close could possibly block the app's UI thread since I'm not waiting on the thread to finish closing the serial port. Any ideas what may be different in this netcf scenario?

    Tuesday, May 29, 2007 2:58 PM
  • The other workaround was to use Control.BeginInvoke() instead of Invoke().  It should work just fine in your case since you're just passing a string.
    Tuesday, May 29, 2007 4:01 PM
    Moderator
  • Ah, sorry, I forgot to tell that I also tried using BeginInvoke() instead of Invoke() but that had no positive effect either. The problem is that I have to process the events sequentially so I used a combination of Control.BeginInvoke() and IAsyncResult.AsyncWaitHandle.WaitOne(). I don't know if that results in similar problems as using Control.Invoke(). In that case, another way to work around that problem could be to cache the EventArgs instances and process them on a separate thread, although that looks pretty complicated for a simple task as reading data from a serial port Wink

    Wednesday, May 30, 2007 8:27 AM
  • Yes, using WaitOne() would undo the benefit of using BeginInvoke().  There is no need to use it, BeginInvoke() is serialized by the message queue.
    Wednesday, May 30, 2007 8:41 AM
    Moderator
  • Well, that's what I assumed. Since Control.(Begin)Invoke() calls the method invoked in the context of the thread that created the control and RaiseEvent should call the connected event handlers synchronously I would have guessed the processing of the raised events to be sequential. So in my test app I just displayed the scanned barcode using a MessageBox hoping other barcodes would be cached by the scanner (or discarded if the cache was full) as long as the MessageBox is not closed. But against my assumption every time I scanned a new barcode another MessageBox would pop up.

     

    I always thought I knew enough about threading and Control.(Begin)Invoke() to avoid such traps but currently I feel a little lost.

    Wednesday, May 30, 2007 12:24 PM
  • MessageBox.Show() doesn't prevent event handlers from running, it just disables mouse and keyboard input.  Displaying a message box in a timer's Tick event does the same thing, you'll quickly have a screen filled with message boxes.  Just use a non-modal form to display the barcode.  Hook its FormClosed event so you know the user closed it and you'll need to create a new instance.  For example:

    Public Class Form2
      Public Property BarCode() As String
        Get
          Return Label1.Text
        End Get
        Set(ByVal value As String)
          Label1.Text = value
        End Set
      End Property
    End Class

    And your main form:

    Public Class Form1
      Private mBarCode As String
      Private WithEvents mBarForm As Form2

      Public Sub ProcessData(ByVal txt As String)
        mBarCode = mBarCode + txt
        '--- Received a full barcode?  Then display it
        If mBarCode.Length >= 16 Then   ' Adjust this as needed
          If mBarForm Is Nothing Then
            mBarForm = New Form2
            mBarForm.Show()
          End If
          mBarForm.BarCode = mBarCode
          mBarCode = ""
        End If
      End Sub
      Private Sub mBarForm_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles mBarForm.FormClosing
        '--- User closed form, make sure we create a new one when we get a new barcode
        mBarForm = Nothing
      End Sub
    End Class


    Wednesday, May 30, 2007 12:43 PM
    Moderator
  • Thank you for the info. I simplified my test application so it looks like this:

     

    Code Snippet

    Private WithEvents p As IO.Ports.SerialPort

     

    Private Sub btnStart_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStart.Click

        If p Is Nothing Then

            p = New IO.Ports.SerialPort("COM4:")

            p.Open()

        End If

    End Sub

     

    Private Sub btnStop_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnStop.Click

        Dim t As Threading.Thread = New Threading.Thread(AddressOf ClosePort)

        t.Start()

    End Sub

     

    Private Sub Form1_Closing(ByVal sender As Object, ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing

        Dim t As Threading.Thread = New Threading.Thread(AddressOf ClosePort)

        t.Start()

    End Sub

     

    Private Sub ClosePort()

        If p IsNot Nothing Then

            p.Dispose()

            p = Nothing

        End If

    End Sub

     

    Private Delegate Sub UpdateDelegate(ByVal str As String)

    Private Sub UpdateLabel(ByVal text As String)

        If Label1.InvokeRequired Then

            Label1.BeginInvoke(New UpdateDelegate(AddressOf UpdateLabel), New Object() {text})

        Else

            Label1.Text = text

            Label1.Refresh()

            ' simulate some work

            Threading.Thread.Sleep(5000)

        End If

    End Sub

     

    Private Sub p_DataReceived(ByVal sender As Object, ByVal e As IO.Ports.SerialDataReceivedEventArgs) Handles p.DataReceived

        While p.BytesToRead > 0

            Dim bc As String = p.ReadLine()

            UpdateLabel(bc)

        End While

    End Sub

     

    I'm using BeginInvoke as well as a separate thread for closing the serial port, still the application freezes at SerialPort.Dispose() until I disable bluetooth on the device thus closing the serial port by force. I'm beginning to suspect some error in Symbol's bluetooth implementation as my application still works on another (HP) device.

    Wednesday, May 30, 2007 1:57 PM
  • I have had this issue with a serial port, and i seem to have fixed it by doing a SerialPort.DiscardInBuffer(); before the close statement. I found that the close only hung when i had actually read from the serial port, and i thought that there may be something left behind in the read buffer.
    Monday, October 08, 2007 11:07 AM
  • I can confirm that 5 years later and using Visual Studio 2012 with .NET Framework 4.5, this is still the only fix that works!
    Wednesday, September 05, 2012 4:47 PM