The main thread of the GUI application is the STA thread, a single-threaded apartment. Note the [STAThread] attribute in the Main () method of your program. STA is the term COM, it gives a hospitable home to components that are fundamentally unsafe, which allows them to be called from a workflow. COM is still very much alive in .NET applications. Drag and drop, clipboard, shell dialogs such as OpenFileDialog, and common controls such as WebBrowser are single-threaded COM objects. STA is a tough requirement for user interface threads.
The behavioral contract for the STA flow is that it must pump the message loop and cannot be blocked. A lock is likely to cause a deadlock, since it does not allow marshaling for these components of threaded COM components. You block the thread using the block statement.
The CLR knows this requirement very well and does something. Blocking calls, such as Monitor.Enter (), WaitHandle.WaitOne / Any (), or Thread.Join (), pumps the message loop. The type of native Windows API that does this is MsgWaitForMultipleObjects (). This message loop sends Windows messages to keep the STAs active, including paint messages. This, of course, can cause redistribution problems, Paint should not be a problem.
There's good info on this in this blog post by Chris Brumme .
Perhaps this all rings the bell, you probably can't help but notice that this is very similar to the application calling Application.DoEvents (). Probably one of the scariest methods available to solve UI freeze issues. That a fairly accurate mental model for what happens under the hood, DoEvents () also pumps up the message loop. The only difference is that the CLR equivalent is a bit more selective as to which messages it allows to send, it filters them. Unlike DoEvents (), which sends everything. Unfortunately, neither the Brumme message nor the SSCLI20 source is detailed enough to know exactly what is being sent, the actual CLR function that makes this unavailable at the source and too large to decompile. But, obviously, you can see that it does not filter WM_PAINT. It will filter real problem creators, add event notifications, such as a view that allows the user to close a window or click a button.
Function, not error. Avoid re-hits by removing blocking and relying on marching callbacks. BackgroundWorker.RunWorkerCompleted is a classic example.