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' 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:
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.
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' 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
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.