If broken it is, fix it you should

Using the powers of the debugger to solve the problems of the world - and a bag of chips    by Tess Ferrandez, ASP.NET Escalation Engineer (Microsoft)

Debugging Puzzler - WinForms crash - Can you figure out what caused it?

Debugging Puzzler - WinForms crash - Can you figure out what caused it?

  • Comments 17

Earlier this week I was doing a presentation and since the company I presented for had mostly winforms developers I wanted to convert all my ASP.NET debugging demos to winforms equivalents for the presentation.

As I was converting my first demo (a performance issue) I ran into a bit of a snag because the first time I ran the demo (which populates 4 datagridviews with product information) things worked fine. I clicked the Featured Products button, my datagrids were nicely filled, I could reproduce my performance issue and debug it.

The second time I clicked the button to reproduce the issue however I was met with this:

WinformsCrash 

Instead of data in my datagrids they were just covered with big red X:es and the application crashed with a NullReferenceException (Object reference not set to an instance of an object) in System.Windows.Forms.DataGridViewRowHeaderCell.PaintPrivate.

I have attached the faulting application to this post if you want to try reproducing and debugging the issue and figure out what happened...  feel free to post your suggestions for causes and resolutions in the comments...

The first comment that leads to a resolution will be dubbed "Master Debugger August 2008":)  I'll post my answer later on ...

Have fun,

Tess

Attachment: DebuggingPuzzler1.exe
  • I bet that you load those DataGridViews from a different thread than the UI thread without using Control.Invoke/BeginInvoke :)

  • Well,

    It seems there is a thread created to bind each of the datagridview objects. In a thread code, there seem to be a sleep call.

    0:009> !clrstack

    OS Thread Id: 0xe80 (9)

    ESP       EIP    

    03dcf2a8 7c82ed54 [HelperMethodFrame: 03dcf2a8] System.Threading.Thread.SleepInternal(Int32)

    03dcf2fc 00c716b5 DataLayer.GetFeaturedProducts()

    03dcf338 00c7163e DebuggingPuzzler1.frmMain.FillFeaturedProducts(System.Object)

    03dcf344 7964a312 System.Threading.ThreadHelper.ThreadStart_Context(System.Object)

    03dcf34c 79373ecd System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)

    03dcf364 7940647a System.Threading.ThreadHelper.ThreadStart(System.Object)

    03dcf58c 79e7c74b [GCFrame: 03dcf58c]

    0:009> !syncblk

    Index SyncBlock MonitorHeld Recursion Owning Thread Info  SyncBlock Owner

      29 0017cc3c            1         1 001ac6b8   e80   9   01252d48 System.Object

    The issue is that there is syncblk object on one thread which is sleeping. While other thread is setting its data source which is in turn dispatching the WM_PAINT message to the datagrid and in turn each of its cells.

    However since the datasource could not be set since the thread does not have a lock object , it seems to fail.

    Specifically I see an invalid reference to memory when the Bottom property of Rectangle object of Cell is retrieved.

    0:008> !clrstack

    OS Thread Id: 0xc94 (8)

    ESP       EIP    

    03ccf314 7c82ed54 [NDirectMethodFrameStandalone: 03ccf314] System.Windows.Forms.UnsafeNativeMethods.SendMessage(System.Runtime.InteropServices.HandleRef, Int32, Int32, Int32)

    03ccf330 7b07dec2 System.Windows.Forms.Control.SendMessage(Int32, Int32, Int32)

    03ccf354 7b416df7 System.Windows.Forms.Control.BeginUpdateInternal()

    03ccf360 7b1c8655 System.Windows.Forms.DataGridView.RefreshColumns()

    03ccf390 7b1ac825 System.Windows.Forms.DataGridView.OnDataSourceChanged(System.EventArgs)

    03ccf3a4 7b17b421 System.Windows.Forms.DataGridView.set_DataSource(System.Object)

    03ccf3b8 00c7166e DebuggingPuzzler1.frmMain.FillFeaturedProducts(System.Object)

    03ccf3c4 7964a312 System.Threading.ThreadHelper.ThreadStart_Context(System.Object)

    03ccf3cc 79373ecd System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)

    03ccf3e4 7940647a System.Threading.ThreadHelper.ThreadStart(System.Object)

    03ccf60c 79e7c74b [GCFrame: 03ccf60c]

    That's all what I could gather :)

  • Tess...It works perfectly on my Vista dev system.

    (VS2008 Pro with most recent updates.)

    I created a Video so you can see:

    http://iis7test.com/debug/debug001.aspx

    Odd?

  • I guess the probability of exception depends on the duration of sleep call ...

  • Wisemx,  in the video you only clicked the Featured Products link once, try clicking it again... btw it's a bit of a timing issue

  • Man thats funny... this is caused by threading issues? I haven't done winform programming since 2003~, back when I was (well, like almost everyone) much less knowledgeable about .NET (though I guess this particular issue is true for most windows app development).

    There I was,  in my newbie self and got that particular issue. Never was able to fix it, and eventually moved to asp.net development, so I never hit it again, nor thought twice about it.

    Only took 5 years, but now I know what caused it :)

  • Looks like you're setting the DataGridView.DataSource on a background thread in FillFeaturedProducts.  Almost every .NET control does not support setting control data on any other thread than the main GUI thread.  Not to mention, DataGridView is documented as "Any instance members are not guaranteed to be thread safe."

    I would suggest marshaling setting DataGridView.DataSource back to the GUI thread. (InvokeRequired/BeginInvoke is one way of doing that).

  • I did have a look with Windbg and the exception stacks. But I cheated and had also a look with reflector ;-).

    There are 4 theads spawned which do bind their data from a slow method. The first time it works since there was no data yet there.

    But the second time the returned DataView

       ((DataGridView) grid).DataSource = view;

    will cause  a window event OnDataSourceChanged. Because that event comes from a different thread it messes the window message loop up which was perhaps already processing some redrawing. In effect one thread alters the data of another thread while he tries to access it. That results in NullReferenceExceptions.

    One solution would be to call the

       ((DataGridView) grid).DataSource = view;

    from the man UI thread via

      Action d = () => {

       ((DataGridView) grid).DataSource = view;

      }

      base.Invoke( d );

    That should do the trick.

    Yours,

     Alois Kraus

  • If you run the application in debug mode it will tell you what is wrong (Cross-thread operation not valid: Control 'dgFeaturedEMEA' accessed from a thread other than the thread it was created on.)

    The solution is an old fashion invoke if invoke is required.

    public void FillFeaturedProducts(object grid)

    {

       DataView view = new DataView(dl.GetFeaturedProducts());

       this.SetDataSource((DataGridView)grid, view);

    }

    private delegate void SetDataSourceDelegate(DataGridView grid, DataView view);

    public void SetDataSource(DataGridView grid, DataView view)

    {

       if (this.InvokeRequired)

       {

           this.BeginInvoke(new SetDataSourceDelegate(SetDataSource), grid, view);

           return;

       }

       grid.DataSource = view;

    }

  • Hi there.

    I'm a real beginner at this, but I ran the app in WinDbg and saw that the exception was being raised here:

    System.Windows.Forms.DataGridViewRowHeaderCell.PaintPrivate

    and I think it's on this line:

    Dim flag As Boolean = ((dataGridViewElementState And DataGridViewElementStates.Selected) <> DataGridViewElementStates.None)

    Since dataGridViewElementState is passed as a parameter to this routine, I can guess that this parameter is Null.

    That's my guess, sorry it's not as deep as the other people's comments.

    Cheers.

    Jas.

  • just ran it under MSVS debugger, and got an exception:

    Cross-thread operation not valid: Control 'dgFeaturedEMEA' accessed from a thread other than the thread it was created on.

    this seems to explain everything %)

  • Link Listing - August 27, 2008

  • WPF Filtering a list of items with the PropertyFilterGroupView control [Via: Josh Smith ] Code Camps...

  • i wonder if the thread that is supposed to populate the datagrid is trying to make use of a brush destroyed by the GUI thread (before redraw or something). I don't remember reading anything about that big red X, not even from the Datagridview team ...

  • Nice debugging everyone:)

    Actually Mike will be "Master Debugger August 2008" since he took it in the very first comment, which was about 5 minutes after I made the post:)  A bit discouraging I might add, I thought it would take a little longer for someone to figure it out:)

    The problem, as Mike (and others) stated, is that I am performing operations on a winforms control from a non-ui thread, which leads to issues like this one since operations on winforms controls are not thread-safe

    The solution, which he also mentioned is to use Control.Invoke/BeginInvoke as described in this article

    http://msdn.microsoft.com/en-us/library/ms171728(VS.80).aspx

    So... it all comes down to me not knowing how things work in a winforms environment:)

Page 1 of 2 (17 items) 12
Leave a Comment
  • Please add 8 and 8 and type the answer here:
  • Post