Recently, my colleague was investigating an interesting issue. A
ReaderWriterLockSlim was sometimes throwing the following exception when releasing a read lock.
System.Threading.SynchronizationLockException: 'The read lock is being released without being held.'
The code looked fairly straightforward and it should have worked properly, but after some meditation on the mysteries of C# and the universe, he noticed something interesting. An await call between taking the lock and releasing it.
Roughly, the problematic code had the following pattern:
var rwl = new ReaderWriterLockSlim();
A suspicion was confirmed by looking at the reference implementation of
ReaderWriterLockSlim - a field that tracks the amount of locks taken is defined like this:
The lock taking code looks like this (omitting some code for clarity):
private bool TryEnterReadLockCore(TimeoutTracker timeout)
That was enough to understand what was the issue. Using async/await yields current thread until the async method finishes execution and then fetches another thread from the thread pool to continue execution - and since
ThreadStatic to store the counts of read/write locks, no wonder it thinks the lock was never taken after async/await call!
The solution in such cases is either removing usage of async/await or utilizing a different set of synchronization primitives that are compatible with thread yields resulting from await calls.
There is excellent series of blog posts on the topic by Stephen Toub, where he shows how async-compatible synchronization primitives can be implemented. The first blog post you can find here.
Also, check out AsyncEx - a very handy library with full set of async-compatible synchronization primitives, so you don’t have to implement your own.