none
Does IsDisposed() still work? RRS feed

  • Question

  • Greetings everyone.

    I have a Form with panels that get refreshed on a timer every few seconds (usually 5). Occasionally there would be a crash with an ObjectDisposedException because the user would close the Form at the exact moment it was being refreshed. I managed to stop the crashes by checking the Form's IsDisposed flag, thus.

          void InvalidateAPanel(Panel panel)
          {
             if(panel.InvokeRequired)
             {
                if (!this.IsDisposed)
                {
                   InvalidateCallback d = new InvalidateCallback(InvalidateAPanel);
                   this.Invoke(d, new object[] { panel });
                }
             }
             else
             {
                panel.Invalidate(true);
             }
             
          }

    That was under VS2010. We were just about to release a new version compiled under VS2017 (we don't rush updates here) and someone noticed the crashes had come back.

    I debugged, and IsDisposed is definitely false, and all the other data I could see suggests the Form has not been disposed, but it's still (sometimes, intermittently) throwing an ObjectDisposedException. And it's definitely the Form that it thinks hasn't been disposed, not the Panel it's trying to refresh.

    We were under a bit of time pressure to get the new release out, so I did a quick and dirty fudge by putting a try-catch block around the offending code and doing nothing in the catch. But I'd rather do a proper fix for the future, if I can find one.

    So, has anybody else heard of this sort of behavior, and is there some recommended proper fix?

    Thanks.

    • Moved by CoolDadTx Wednesday, July 3, 2019 2:08 PM Winforms related
    Thursday, June 27, 2019 2:21 AM

All replies

  • Hi Ante,

    Thank you for posting here.

    Based on your description, I make a simple test for it. I don't see the exception. 

    >>I debugged, and IsDisposed is definitely false

    I think it is right, If IsDisopsed is true, the form will not be used again. If you want to know more about it, please refer to Control.IsDisposed property.

    Test code:

            int i = 1;
            TextBox text = new TextBox();
    
            private void Button1_Click(object sender, EventArgs e)
            {
                timer1.Interval = 5000;
                timer1.Start();
      
            }
    
            private void Timer1_Tick(object sender, EventArgs e)
            {
                text.Text = "panel" + i.ToString();
                text.Location = new Point(20 * i, 20 * i);
                panel1.Controls.Add(text);
                i++;
                MessageBox.Show(panel1.IsDisposed.ToString());
            }

    Result:

    Best Regards,

    Jack


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Thursday, June 27, 2019 6:32 AM
  • Thanks for that effort Jack, but I don't think you understand.

    Naturally IsDisposed will be false while the Form is visible and the timer is running. That's not a problem.

    The problem is that, sometimes (just occasionally) the user will close the form down just as the timer ticks. In that case, the code that refreshes the Panels on the Form can be called after the Form has been disposed (apparently).

    I used to be able to trap that case by testing IsDisposed (as per the code I posted), but now after the change to VS2017 the trap doesn't work. Now I get an ObjectDisposedException on the Invoke line even though IsDisposed is false.

    And to repeat, it doesn't happen every time. A user can open and close the form twenty or thirty times with no problems, then suddenly they will do it again and the program will crash when they close the Form.

    So my question is about whether the IsDisposed behaviour has changed. How is it possible to get an ObjectDisposedException on an object when IsDisposed is false?

    Thursday, June 27, 2019 6:53 AM
  • And in case you don't believe me, here's a screenshot of the exception (with my dodgy try-catch commented out).

    Thursday, June 27, 2019 7:07 AM
  • I can see this as a race condition. You didn't specify what type of timer you were using (Winforms, System.Threading, etc) but let's assume the timer can fire after the form is closed. Can we also assume you updated framework versions?

    When you call Close on the form this sends a WM_CLOSE message to your window. Because it is the same thread it goes directly to the WndProc. This eventually calls WmClose which then triggers the FormClosing and FormClosed events. After all the events are raised, and nobody cancels, then it calls Dispose.

    Now imagine that during the time between your code calling Close and it actually calling Dispose your timer fires. If this is a Winforms timer then it would block waiting on the message queue (which is waiting for the Close to finish). But for the other times it determines an invoke is required and calls Invoke. The documentation for Invoke indicates that an exception can occur if the thread associated with the control is gone. But let's assume that it is still valid so this method is called on the UI thread. Since InvokeRequired is false it jumps to Invalidate which pushes a WM_PAINT message into the queue. However if the panel has already been disposed then it'll throw an exception.

    I think you might be able to lessen the odds of this happening by moving your IsDisposed check outside the inner loop. The thing I find odd is that looking at the framework code IsDisposed is just look at the underlying state variable but I don't see anyplace in the code that actually sets it. It isn't done anywhere in the close/dispose logic so if it is being set then it is happening as a result of something else (perhaps form visibility) so when this property gets moved to true is unclear.


    Michael Taylor http://www.michaeltaylorp3.net

    Wednesday, July 3, 2019 2:08 PM

  • Sorry for late answer. About "Control.IsDisposed", I do not think it is done at the same time as "Control.Parent.IsDisposed" and as you call Form.IsDisposed from a separate threard it may be not disposed but the control contained by form to be disposed, so you should check it for each Control / Form separately.

    Ex.

    Nr Main Thread Second Thread
    1. Form.Dispose()
    2. Control1 disposed
    3. Your call
    4.0. Exception
    4.1. Time when form is disposed

    If I understood right, your code looks like this:
    Control1.Invoke(Action_to_change_Control2)

    Maybe is better to use it like this?:

    Control2.Invoke(Action_to_change_Control2)

     Or you can use an extension like this:

    using System; 
    
    namespace Test
    {
    
        public partial class Form1 : Form
        {
            private void Button1_Click(object sender, EventArgs e)
            {
                // Use extension like this
                pannel1.Invoke(() => pannel1.BackColor = Color.Black);
    
                // Or like this... 
                pannel1.Invoke(() =>
                {
                    pannel1.BackColor = Color.Black;
                    pannel1.Size = new Size(200, 300);
                });
            }
        }
    
        public static class ControlExtensions
        {
            public static void Invoke(this Control Control, Action Action)
            {
                if (Control.InvokeRequired && !Control.IsDisposed)
                {
                    Control.Invoke(Action);
                }
                else if (!Control.IsDisposed)
                {
                    Action();
                }
                else
                {
                    //Error
                }
            }
        }
    }

    But most important do not invoke Control1 when you want to change something on Control2 .

    PS. Sorry for my bad english.


    • Edited by IoanSh Monday, December 2, 2019 9:12 PM Css not working
    Monday, December 2, 2019 9:11 PM