debugging - a late Christmas story

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,800
Location
Norway
Programming Experience
10+
I just spent 3 hours debugging and trying out different approaches to achieve the goal. The project was some kind of email application about 2000 lines of code (excluding generated). The mission was to rewrite it from single-threading to multi-threading, which isolated was not difficult. All objects are written as classes and the app code combine class instances when needed, for example pairing instances of account, connection and email classes to get online and update emails for an account. Emails are stored in a shared dictionary by string key uid, shared conveniently because it is used by different classes and routines and I found it got real messy passing this object all around.

Now to the problem that arised, I ran the update routine asynchronous with a delegate and callback. The socket update accessed the emails dictionary and finished ok, the callback invoked on the UI thread to update a Listview but errored out every time on the invoke statement saying I couldn't access the dictionary by a key that was 'null'. I tracked the error occuring right after a new item was added to the Listview, but the Listview update did not access the dictionary by key.

Searching everywhere in the update routine, which by it self is 'only' a few hundred lines of code I found nothing. I double checked every usage of the dictionary and made sure there wasn't a thread conflict by using the SyncLock statement on the object. Continued search to related classes that might be doing 'undocumented' :eek: calls to the collection while processing under different threads. No go at all, as mentioned I also tried different approaches of callbacks/events to make the UI job happen, I started wondering if there could be a bug with delegate invoke and shared object instance or something, perhaps even that the other thread object was released and thread was firing back at loose ends there - really crazy debugging paranoia.

Then after 'resting' a while I suddenly remembered a minor tweak I did when converting the project from single-thread to multi-thread, instead of initiating the socket update and the UI update from same method I separated those and needed one additional info about the listview email item now that the Control was out of reach from other threads. I needed info about whether the email ListViewItem was checked or not stored in the email object instance. This was simply solved by adding the Listview ItemChecked event handler, something like:
VB.NET:
emails(e.Item.Tag).isChecked = e.Item.Checked
Notice the item tag is used to connect the email uid, this property was set in Listview update before setting the Checked property. But the exception occured immediately when adding the new item to the Listview. This was it! When initializing the ListView.ItemChecked event triggers but the Tag is still reading a 'null', and since I was on a cross-thread invoke the debugger knew nothing but to tell me that the invoke failed, only hint was the mysterious "Value cannot be null. Parameter name: key". Escaping this by checking if e.Item.Tag was empty succeded the full update.

In every Christmas story there's some moral, in this one perhaps also; don't forget the events when debugging, sometimes the code is perfect but it may trigger other things to happen, Windows forms is event based, and they may trigger for different reasons than you expect. And don't forget the little things, in this case is was a single line of code quickfix because of reordering some code, but it hid and stopped the whole application coming from another end. Sometimes the debugger can't help you identifying the object or line of code that caused the error. Also don't expect data not created by yourself to be this or the other kind, check to know. That's a lot of moral for one little bugger.
 
Quick question: If you'd set the debugger to break the instant that an exception was raised, by use of Debug menu >> Exceptions.. (See screenshot), would the code have broken at the instant an attempt to reference a collection (in this case emails() ) with a null key?
Or would it have broken at the point the Invoke started?

I dont know if it would have helped you in this case but I frequently find that breaking on throw rather than user-unhandled is of great help..
 

Attachments

  • Image2.png
    Image2.png
    6.3 KB · Views: 60
Yes, thank you for the reminder! We actually discussed this feature some other time (here), but I forgot about those options. Adding the Thrown on CLR System.ArgumentNullException did break to the relevant code line. Would have saved me a few hours frustrations, but I did learn a few new 'tricks' with delegates and threads on the go so the experience wasn't all wasted :) I'm not sure about the reasoning why this particular (user-unhandled) exception fell all the way back to the control/thread invokation line, though.
 
I'd guess that the exception bubbled all the way up as the call stack for that particular thread unwound, and when it eventually reached the point where that thread terminated, it was passed to the parent thread that had caused it to run. As it transitioned the boundary, the call stack detail was lost, possibly because a new exception was raised with the message of the old one but none of the call stack?

I'd like to thank you for taking the time out to share the experience though; its sure gonna be handy for me as I've just taken on a project where cross-thread exceptions are a real possibility :D
 
Back
Top