I came across something I simply don\'t understand. In my application I have several threads all adding (and removing) items to a shared collection (using a shared lock). Th
The main thread of a GUI app is an STA thread, Single Threaded Apartment. Note the [STAThread] attribute on the Main() method of your program. STA is a COM term, it gives a hospitable home to components that are fundamentally thread-unsafe, allowing them to be called from a worker thread. COM is still very much alive in .NET apps. Drag and drop, the Clipboard, the shell dialogs like OpenFileDialog and common controls like WebBrowser are all single threaded COM objects. STA is a hard requirement for UI threads.
The behavioral contract for an STA thread is that it must pump a message loop and is not allowed to block. Blocking is very likely to cause deadlock since it doesn't allow the marshaling for these apartment threaded COM components to progress. You are blocking the thread with your lock statement.
The CLR is very much aware of that requirement and does something about it. Blocking calls like Monitor.Enter(), WaitHandle.WaitOne/Any() or Thread.Join() pump a message loop. The kind of native Windows API that does that is MsgWaitForMultipleObjects(). That message loop dispatches Windows messages to keep the STA alive, including paint messages. This can cause re-entrancy problems of course, Paint should not be a problem.
There's good backgrounder info on this in this Chris Brumme blog post.
Maybe this all rings a bell, you probably can't help notice that this sounds a lot like an app calling Application.DoEvents(). Probably the single-most dreaded method available to solve UI freezing problems. That's a pretty accurate mental model for what happens under the hood, DoEvents() also pumps the message loop. The only difference is that the CLR's equivalent is a bit more selective about what messages it allows to be dispatched, it filters them. Unlike DoEvents() which dispatches everything. Unfortunately neither Brumme's post nor the SSCLI20 source is sufficiently detailed to know exactly what is getting dispatched, the actual CLR function that does this is not available in source and far too large to decompile. But clearly you can see that it does not filter WM_PAINT. It will filter the real trouble-makers, input event notifications like the kind that allows the user to close a window or click a button.
Feature, not a bug. Avoid re-entrancy headaches by removing the blocking and relying on marshaled callbacks. BackgroundWorker.RunWorkerCompleted is a classic example.