Dumbo
Dumbo

Reputation: 14102

What causes my UI to freeze when closing a serial port?

I am working on a serial port related application. While using DataReceived event of SerialPort I need to update a textbox with the received bytes:

private void Connection_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    var data = Connection.ReadExisting();
    _readBuffer.Add(indata);

    Invoke(new EventHandler(AddReceivedPacketToTextBox));       
}

So I use Invoke to update the textbox. But there is a big problem. When I try to close connection, my UI gets freezed, I think this is becase Invoke is doing somehing perhaps.

A friend said I should use RequiredInvoke, but I have no idea what he ment really. How can I close the connection without messing up the invoke and UI thread?

Here is my close method:

private void DisconnectFromSerialPort()
{
    if (Connection != null && Connection.IsOpen)
    {
        Connection.Close();            
    }
}

UPDATE

As Hans said I changed Invoke to BeginInvoke but now its a bit worse, my application stops working due to InvalidOperationException because the collection _readBuffer was modified (Thats what the detail says in VS)

Here is my code for adding text to textbox:

private void AddReceivedPacketToTextBox(object sender, EventArgs e)
{

    foreach (var i in _readBuffer)
        tbIn.Text += string.Format("{0:X2} ", i);

    tbIn.Text += Environment.NewLine;

    ScrollToBottom(tbIn);

    label4.Text = _receivedPackets.ToString();
    _receivedPackets++;

    _readBuffer.Clear(); //Possibly because clearing collection gets out of sync with BeginInvoke??         
}

2nd Update

I still have the problem, changing the Invoke() to BeginInvoke didn;t help. I also tried to add disconnect to form closing event nu success...anytime I close my form it gets stock (I mean its parent form, because this form that has access to serialport is being called from another form`.

I mean I figured out that the UI gets locked only in 2 cases: If I clock a button which calls Connection.Close() also if I try to close the form, the parent form will throw exception that some objects are disposed.

I call the serial form like this from the parent form:

public DebugForm DebugForm;

private void button1_Click(object sender, EventArgs e)
{
    if (DebugForm != null)
    {
        DebugForm.BringToFront();
        return;
    }

    DebugForm = new DebugForm();
    DebugForm.StartPosition = FormStartPosition.CenterScreen;
    DebugForm.Closed += delegate
                             {
                                 WindowState = FormWindowState.Normal;
                                 DebugForm = null;
                             };

    DebugForm.Show();
    WindowState = FormWindowState.Minimized; 
}

Could this be the problem?!

Upvotes: 2

Views: 5141

Answers (2)

Dumbo
Dumbo

Reputation: 14102

The problem could be solved by adding a timer:

  bool formClosing = false;
    private void Connection_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
      if (formClosing) return;
      _buffer = Connection.ReadExisting();
      Invoke(new EventHandler(AddReceivedPacketToTextBox));
    }
    protected override void OnFormClosing(FormClosingEventArgs e)
    {
      base.OnFormClosing(e);
      if (formClosing) return;
      e.Cancel = true;
      Timer tmr = new Timer();
      tmr.Tick += Tmr_Tick;
      tmr.Start();
      formClosing = true;
    }
    void Tmr_Tick(object sender, EventArgs e)
    {
      ((Timer)sender).Stop();
      this.Close();
    }

Thanks to JohnWein from MSDN

Upvotes: -1

Hans Passant
Hans Passant

Reputation: 941218

Yes, this code has very high odds of causing deadlock on the Close() call. The serial port cannot close until the DataReceived event handler stops running. But the Invoke() call cannot complete until the UI thread goes idle and pumps the message loop. It isn't idle, it is stuck in the Close() call. So the event handler cannot make progress because it is stuck in the Invoke() call and your main thread cannot make progress because it is stuck in the Close() call, deadlock city.

The best workaround is to use BeginInvoke() instead, that doesn't block the event handler. Not closing the serial port at all is another workaround, which is okay since Windows takes care of it automatically when your program terminates. In general, closing a serial port while the device is busy sending data is iffy and inevitably causes data loss. Okay when you're debugging your code but not something you'd normally like to see happening in production.

Upvotes: 7

Related Questions