I’ve written a number of times in this blog about deadlock; but I don’t know that I’ve ever given a concrete example illustrating how it can actually occur in code.
The unfortunate truth is that it’s easier to stumble upon than you might think. What I will give shortly is an example using some very common tools many .NET developers should be familiar with: the Windows Forms API and a simple WaitHandle.
But first, a refresher on what deadlock is.
Deadlock happens when two processes are waiting on each other. In the cartoon above, the boy will not reveal what happened (obvious though it may be) until his mother promises not to be mad. His mother, in turn, can’t make any such promise until she knows what happened.
In a Windows Forms application, it is disturbingly easy to fall into the trap of writing code that results in a deadlock. I find that this generally happens with developers who, to borrow one of my mom’s expressions, “know just enough to be dangerous” about multithreading. These are the kinds of developers who know about the
lock statement, about threads and mutexes and wait handles, etc., but who aren’t exactly sure how to wield these tools in a totally safe way.
In all honesty, I have to count myself among these developers, since I can’t say I am so good at multithreading that I never make mistakes in this realm. (To be brutally honest, I don’t think anyone is that good… but I’ve talked about that before.)
So, how can deadlock happen so easily in a Windows Forms app? Simple: through a single careless use of the
To understand this, it’s necessary to first understand how Windows Forms actually works. It’s actually pretty straightforward: Windows Forms basically consists of a message pump:
A Windows application is little more than a queue of messages being sent to a window. Every action performed by the user—moving the mouse, hitting a key on the keyboard, clicking on a button, etc.—pushes a “message” into a queue, while Windows continuously pops these “messages” from the queue and executes whatever code a programmer has associated with each message.
(In Windows Forms, these messages are exposed in the form of .NET events:
Now, a crucial point that many rookie developers miss is that this queue is being processed by a single thread, typically referred to as the “UI thread” or “foreground thread” (these are not formal terms, as far as I know). It needs to be this way because, like probably 95% of software components in existence, the controls in the
System.Windows.Forms namespace were not designed to be thread-safe when accessed concurrently from multiple threads. And so calls to methods that affect UI controls—like adding to a
ListBox, setting the text of a
TextBox, etc.—must be made from the UI thread; otherwise, it will be pandemonium.
So whenever one wants to update the UI from a background thread (by the way, in my personal opinion, you should just never do this at all—this almost always indicates overly tight coupling or an otherwise problematic design—but that’s a subject for another post altogether), the most common way to do so is by calling
This is what that does:
- Pushes a new message onto the message queue
- Waits for that message to be processed
Uh oh… there is a scary word there: “Waits.” Any scenario where your program is waiting is one where deadlock may creep in, if you aren’t careful.
And so here at last is that concrete example I promised (you can grab this in full project form from a new repository I’ve created on GitHub for the sole purpose of hosting code I share on this blog):
Imports System.Threading Public Class DeadlockExampleForm Private _unlocked As New ManualResetEvent(False) Private Sub BigButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BigButton.Click Dim t As New Thread(AddressOf Deadlock) t.Start() Me.Text = "Entering Deadlock" ' This line will wait forever. _unlocked.WaitOne() End Sub Private Sub Deadlock() ' Invoke is a blocking call. This thread will not continue past this ' line until the Click event handler above runs. Until _unlocked is ' signalled below, this will never happen. Invoke(New Action(AddressOf NotifyComplete)) ' This line will never be reached. _unlocked.Set() End Sub Private Sub NotifyComplete() Me.Text = "Escaped Deadlock" End Sub End Class
See how easy that is? That’s seriously all that needs to happen for deadlock to strike a Windows Forms application: you call
Invoke from a background thread, and from the UI thread you wait on something that happens after that
Invoke call on the background thread. Now the background thread is waiting (because the message pushed by the
Invoke call is not yet processed), and the UI thread is waiting (because the background thread hasn’t done whatever the UI thread is waiting for). Disaster.
Full disclosure: yes, I am familiar with this problem because it has bitten me in the past. Don’t let it bite you!