locked
Avoiding ObjectDisposedException while using Invoke RRS feed

  • Question

  • Hi all,

    I have 2 forms, one is MainForm and second is DebugForm. The MainForm has a button that sets up and shows the DebugForm like this:

            private DebugForm DebugForm; //Field
            private void menuToolsDebugger_Click(object sender, EventArgs e)
            {
                if (DebugForm != null)
                {
                    DebugForm.BringToFront();
                    return;
                }
    
                if(Connection !=null && Connection.IsOpen)
                    DebugForm = new DebugForm(Connection);
                else
                    DebugForm = new DebugForm();
                
                DebugForm.StartPosition = FormStartPosition.CenterScreen;
                DebugForm.Closed += delegate
                {
                    WindowState = FormWindowState.Normal;
                    DebugForm = null;
                };
    
                DebugForm.Show();
                WindowState = FormWindowState.Minimized;
            }

    In the DebugForm, I append a method to handle the "DataReceived" event of the serialport connection (in DebugForm's constructor):

            public DebugForm(SerialPort connection)
            {
                InitializeComponent();
                Connection = connection;
                Connection.DataReceived += Connection_DataReceived;
            }


    Then  in the Connection_DataReceived method, I update a TextBox in the DebugForm, that is using Invoke to do the update:

            private void Connection_DataReceived(object sender, SerialDataReceivedEventArgs e)
            {           
                _buffer = Connection.ReadExisting();
                Invoke(new EventHandler(AddReceivedPacketToTextBox));
            }

    But I have a problem. As soon as I close the DebugForm, it throws an ObjectDisposedException on the "Invoke(new EventHandler(AddReceivedPacketToTextBox));" Line.

    How can I fix this? Any tips/helps are welcome!



    Thursday, October 18, 2012 1:42 PM

Answers

  • I tried again using your code, this time my DebugForm just gets freezed on close and even no exepction being thrown!

    How are you preventing the DataReceived event from being fired before the Form is shown? 

    I can't find anything that works reliably without a timer.  Why fight it.

        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();
        }

    The timer interval has to be longer than the longest time possible in the DataReceived event. The default 100ms should be adequate.

    This should work just as well without the timer since only one method can be executing on the UI thread at a time:

        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;      
          formClosing = true;
          this.Invoke(new MethodInvoker(DelayedClose));
        }
        void DelayedClose()
        {
          this.Close();
        }
    


    • Edited by JohnWein Thursday, October 18, 2012 11:04 PM
    • Marked as answer by Saeid87 Saturday, October 20, 2012 1:46 PM
    Thursday, October 18, 2012 9:50 PM

All replies

  • You must disconnect from the DataReceived event during DebugForm's FormClosing event.

    Jose R. MCP
    Code Samples

    Thursday, October 18, 2012 1:56 PM
  • I have tried that...it didn't help :(
    Thursday, October 18, 2012 3:15 PM
  • In that case most likely the connection is firing events faster than expected.  You'll have to wait for the connection to close during FormClosing in order to avoid the error.  I suppose there's a method in that SerialPort class that you can use to close the connection.

    But if you cannot close the connection, then use a control variable:  Add private bool _formClosed = false; private object _sync = new object(); at the class level in the debug form CS file, and then set the _formClosed variable to true in FormClosing making sure you use lock(_sync){ } to achieve thread synchronization.  Then check this value in the DataReceived event (again, using lock(_sync) { }) before attempting to call Invoke().  If _formClosed is true just don't call Invoke().


    Jose R. MCP
    Code Samples

    Thursday, October 18, 2012 3:43 PM
  • You can't indiscrimately close the SerialPort while you've invoked a method on the UI from the DataReceived event.  Closing the Port aborts the DataReceived thread, so ensure that you aren't in the invoked method when you close the port.
    Thursday, October 18, 2012 5:50 PM
  • You can't indiscrimately close the SerialPort while you've invoked a method on the UI from the DataReceived event.  Closing the Port aborts the DataReceived thread, so ensure that you aren't in the invoked method when you close the port.

    Right. I should not close the port in any case, The DebugForm just shows the received/send packets thats why I pass a reference of an already open serialport class to it. So closing the serial port within DebugForm is out of equation.
    Thursday, October 18, 2012 6:53 PM
  • In that case most likely the connection is firing events faster than expected.  You'll have to wait for the connection to close during FormClosing in order to avoid the error.  I suppose there's a method in that SerialPort class that you can use to close the connection.

    But if you cannot close the connection, then use a control variable:  Add private bool _formClosed = false; private object _sync = new object(); at the class level in the debug form CS file, and then set the _formClosed variable to true in FormClosing making sure you use lock(_sync){ } to achieve thread synchronization.  Then check this value in the DataReceived event (again, using lock(_sync) { }) before attempting to call Invoke().  If _formClosed is true just don't call Invoke().


    Jose R. MCP
    Code Samples


    Thanks again, but just did as you said, but now the Invoke is never gets called and nothing happens in the TextBox.
    Thursday, October 18, 2012 6:59 PM
  • You can't indiscrimately close the SerialPort while you've invoked a method on the UI from the DataReceived event.  Closing the Port aborts the DataReceived thread, so ensure that you aren't in the invoked method when you close the port.


    Right. I should not close the port in any case, The DebugForm just shows the received/send packets thats why I pass a reference of an already open serialport class to it. So closing the serial port within DebugForm is out of equation.

    How are you ensuring that you are not in the invoked method when the form closes?

    Easiest, is to remove the event handler, cancel the closing and fire a Forms.Timer to close the Form.  Use a Boolean or check the event hander list, to determine whether the event handler has been removed or not.

    • Edited by JohnWein Thursday, October 18, 2012 7:09 PM
    Thursday, October 18, 2012 7:00 PM
  • I managed to partly the problem in this way:

        private delegate void SetTextDeleg(string text); 
     
        void sp_DataReceived(object sender, SerialDataReceivedEventArgs e) 
        { 
            string data = serialPort1.ReadExisting();    
            // Invokes the delegate on the UI thread, and sends the data that was received to the invoked method. 
            // ---- The "si_DataReceived" method will be executed on the UI thread which allows populating of the textbox. 
            this.BeginInvoke(new SetTextDeleg(si_DataReceived), new object[] { data }); 
        } 
     
        private void si_DataReceived(string data)
     { 
    textBox1.Text = data.Trim(); 
    } 
     

    Taken from here


    • Edited by Saeid87 Thursday, October 18, 2012 8:13 PM
    Thursday, October 18, 2012 7:37 PM
  • I managed to solve the problem in this way:

        private delegate void SetTextDeleg(string text); 
     
        void sp_DataReceived(object sender, SerialDataReceivedEventArgs e) 
        { 
            string data = serialPort1.ReadExisting();    
            // Invokes the delegate on the UI thread, and sends the data that was received to the invoked method. 
            // ---- The "si_DataReceived" method will be executed on the UI thread which allows populating of the textbox. 
            this.BeginInvoke(new SetTextDeleg(si_DataReceived), new object[] { data }); 
        } 
     
        private void si_DataReceived(string data)
     { 
    textBox1.Text = data.Trim(); 
    } 
     

    Taken from here

    Thanks for inputs though!

    As long as your data arrives very infrequently, that will work, but the DataReveived event will continue to fire and post messages while the textbox is being updated.
    Thursday, October 18, 2012 8:05 PM
  • As long as your data arrives very infrequently, that will work, but the DataReveived event will continue to fire and post messages while the textbox is being updated.


    Yes I noticed this problem. So what can I do after all?! I dont know how to implement that timer and how to deal with event handler list...would you mind giving a bit more details please?!
    Thursday, October 18, 2012 8:14 PM
  • As long as your data arrives very infrequently, that will work, but the DataReveived event will continue to fire and post messages while the textbox is being updated.


    Yes I noticed this problem. So what can I do after all?! I dont know how to implement that timer and how to deal with event handler list...would you mind giving a bit more details please?!
    Since you don't want to close the port, try just removing the handler in the FormClosing event.
    Thursday, October 18, 2012 8:25 PM
  • As long as you do the control variable option as I stated it should work properly.

    //At class level:
    private bool _isClosing = false;
    private object _sync = new object();
    
    
    //Then in FormClosing:
    protected void DebugForm_FormClosing(object sender, EventArgs e)
    {
        lock(_sync)
        {
            _isClosing = true;
            //Also disconnect the DataReceived event handler.
        }
    }
    
    //And in DataReceived:
    private void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        lock(_sync)
        {
            if (_isClosing) return;
            _buffer = Connection.ReadExisting();
            //With the handler removed, I believe it is safe to have Invoke() inside the lock.
            Invoke(...);
        }
    }
    
    //And just to be 100%, add:
    lock(_sync)
    {
        if (_isClosing) return;
    }
    //to the beginning of function that updates the textbox.
    


    Jose R. MCP
    Code Samples



    • Edited by webJose Thursday, October 18, 2012 8:36 PM
    Thursday, October 18, 2012 8:30 PM
  • I tried again using your code, this time my DebugForm just gets freezed on close and even no exepction being thrown!

    Thursday, October 18, 2012 8:40 PM
  • I tried again using your code, this time my DebugForm just gets freezed on close and even no exepction being thrown!

    How are you preventing the DataReceived event from being fired before the Form is shown? 

    I can't find anything that works reliably without a timer.  Why fight it.

        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();
        }

    The timer interval has to be longer than the longest time possible in the DataReceived event. The default 100ms should be adequate.

    This should work just as well without the timer since only one method can be executing on the UI thread at a time:

        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;      
          formClosing = true;
          this.Invoke(new MethodInvoker(DelayedClose));
        }
        void DelayedClose()
        {
          this.Close();
        }
    


    • Edited by JohnWein Thursday, October 18, 2012 11:04 PM
    • Marked as answer by Saeid87 Saturday, October 20, 2012 1:46 PM
    Thursday, October 18, 2012 9:50 PM
  • I tried again using your code, this time my DebugForm just gets freezed on close and even no exepction being thrown!

    How are you preventing the DataReceived event from being fired before the Form is shown?  What happened when you removed the handler?  That should work for most patterns using ReadExisting and invoke.  It does for a GPS app.

    Well in my MainForm I always check if reference to DebugForm is null or not, its always creating a new instance, and there is a delegate to FormClosed event so after DebugForm gets closed its reference in MainForm will be null.

    I also apply the

    Connection.DataReceived -= Connection_DebugDataReceived;

    in the DebugForm's Load event.

    Funny thing is as soon as I open the DebugForm, if my serial device was turned on, I get a whole amount of data into my textbox, I guess this comes from the internal buffer of the device, or the serial port itslef ?!

    ****

    I added a button to my DebugForm to just detach the event, and now it seems it works good and I can close DebugForm without it throwing exception:

            private void button1_Click(object sender, EventArgs e)
            {
                Connection.DataReceived -= Connection_DebugDataReceived;
            }

    I still would love to now JohnWein's soloution with Timer please.

    • Edited by Saeid87 Thursday, October 18, 2012 10:36 PM update details
    Thursday, October 18, 2012 10:33 PM
  • Thanks a lot JohnWein! works like a charm!
    Saturday, October 20, 2012 1:46 PM