Debugging Puzzler - WinForms crash - Can you figure out what caused it?
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:
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
Comments
Anonymous
August 27, 2008
I bet that you load those DataGridViews from a different thread than the UI thread without using Control.Invoke/BeginInvoke :)Anonymous
August 27, 2008
The comment has been removedAnonymous
August 27, 2008
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?Anonymous
August 27, 2008
I guess the probability of exception depends on the duration of sleep call ...Anonymous
August 27, 2008
Wisemx, in the video you only clicked the Featured Products link once, try clicking it again... btw it's a bit of a timing issueAnonymous
August 27, 2008
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 :)Anonymous
August 27, 2008
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).Anonymous
August 27, 2008
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 KrausAnonymous
August 27, 2008
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; }Anonymous
August 27, 2008
The comment has been removedAnonymous
August 27, 2008
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 %)Anonymous
August 27, 2008
Link Listing - August 27, 2008Anonymous
August 27, 2008
WPF Filtering a list of items with the PropertyFilterGroupView control [Via: Josh Smith ] Code Camps...Anonymous
August 27, 2008
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 ...Anonymous
August 27, 2008
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:)Anonymous
August 28, 2008
Debugging it under Visual Studio flags these errors right away: "Cross-thread operation not valid: Control 'dgFeaturedAmericas' accessed from a thread other than the thread it was created on."Anonymous
May 06, 2010
The reply by Patrik is really awesome. Saved me many hours of work. Patrik the example you gave made it so simple for me. Really you should have separate tutorial on the use of Control.Invoke and Control.InvokeRequired on net.