A concurrency bug rarely announces itself with an error message. It shows up as two bookings for the same appointment, as a counter that is wrong, or as an app that suddenly stops answering, with no exception in the log and almost no processor load. This lesson covers the three most common patterns, shows how you reproduce each of them on demand and how you use tools to see where the threads stand. And it shows the most important experience in this field: the repair is almost never "one more lock", but a different design.
Three patterns
Check-then-act. Code checks a condition and then acts: "Is 10 o'clock free? Then I'll book it." Between checking and acting there is a moment, and in that moment another thread can make the same check and get the same result. Both book. Every single line is right, only the sequence is not.
Lost updates. Two threads read the same value, change it, write it back; one overwrites the other. The
count++ from lesson 3.11.4 is the smallest case, a contact that two people edit at the same time the biggest. In the database you know
this already: optimistic concurrency with a version column and DbUpdateConcurrencyException from K2 is the answer to it.
Deadlock. Two threads wait for each other, each holds what the other needs. Neither gets anywhere, forever. Two forms almost always occur in .NET code:
- Locks in a different order. Thread A takes lock 1, then 2; thread B takes lock 2, then 1. When both have their first, each waits for the other's second.
- Waiting synchronously for something asynchronous in a context with one thread.
.Resultblocks the only thread of a context, and the continuation that would deliver the result waits for permission to run on exactly that thread.