There's two parts to this post, both related by the fact that they both contributed to a bug in my code that's been around for a few weeks that I was unable to figure out until only today.
The first, and perhaps most obvious problem, was something I was not aware of about the BackgroundWorker.ReportProgress (and its corresponding BackgroundWorker.ProgressChanged event. You see, if an exception is thrown inside your ProgressChanged event handler, you'll take out your whole UI thread with a "0xC0000005" error code.
Now, this problem was hard for me to track down for a couple of reasons (the main one being that my brain apparently wasn't switched on...). First of all, the stack trace of the crash just looks it all happened in native code (which is a danger of native code in general -- it hides quite a lot). Second-of-all, I knew the error happened on the call to BackgroundWorker.ReportProgress, but I never thought to put a breakpoint in my ProgressChanged event handler, because I thought -- incoreectly -- that a simple "F10" should have been enough.
Of course it wasn't, and the reason is quite obvious in retrospect. The call to ReportProgress happens in the background thread, but ProgressChanged is fired in the UI thread. The debugger can't track such cross-thread calls (especially since there's a managed/native boundry in between).
Anyway, I finally figured out that the problem was that the type of the object I was passing in as my UserState was different to what the ProgressChanged event handler was expecting and it was throwing an InvalidCastException when I tried to cast it.
So that brings me on to the other part of this post: relying on your tools too much.
You see, I use ReSharper almost religiously and I believe it's background-compilation feature is God's gift to programmers.
One of the problems that it picks up is, if you add a local variable to a method that conflicts with (i.e. has the same name as) a member variable, it flags it as a warning. This is not technically a bug, of course, but it can lead to problems later... as we'll see ;)
Usually, when that happens, I'll just rename the variable to something else, or rethink my design a bit, because it can be a hassle later when you're trying to figure out "is this supposed to refer to the local variable or the member variable?" Some people come up with fancy naming conventions to get around it (like prefixing member variables with an underscore) but I don't like that, personally. It, to me, is just hiding a bigger design problem.
Now, that's the most common case -- adding a local with the same name as a member -- but what happens in the less-common case, where you add a new member variable which conflicts with a local variable somewhere else in your source?
I'll tell you what happens. ReSharper flags the local variable as a conflict. Now, I think it makes sense from a technical perspective -- the background compiler compiles the whole file in the background and it only knows the location of the local variable (at the point it compiles the method) not nessecarily the location of the variable it conflicts with -- but from a UX perspective, the result is less-than-ideal.
Because this brings us back to my original problem. You see, I had a local variable, which is what I was originally passing to my ReportProgress method. I then added a member variable with the same name, but because ReSharper didn't flag the member as a warning (and I didn't notice the new yellow line indicating a warning down near the local variable) I didn't worry about it straight away. It was only after I'd already implemented the rest of the feature that needed the member variable that I noticed the "conflicting variable" warning.
So I renamed the local variable, but because a) there was still a member with the same name and b) ReportProgress takes an object as it's UserState parameter, the ReportProgress was not flagged as an error. So the error only showed up after I ran the program.
I guess it could also imply that I'm not relying on my tools enough -- after all, I could have used the "Rename..." refactoring to rename the local automatically, but I was lazy and it was easier to just type in a new name. I guess I'll just be more careful next time.