Recently working on a project, I came across a scenario where I needed to lock a block so that only one thread could enter the block at a time. Simple Monitors, I thought. However as soon as I put the C# lock statement around my block, I noticed Visual Studio started complaining. The problem was that the block contained an await statement like so:
1 2 3 4 5 |
This was a big head scratcher. I mean, the whole reason we use async/await is because we are spinning threads to go do some work and then invoke the UI thread back on the callback, right? So effectively we are creating multi-threaded programs, right? So we need synchronization primitives, right? So the C# lock statement should work, right?
Grrr! That does not work.
Now what?
I am certainly not the first person to hit this issue.
Someone must have done it already. Let’s Bing!
So, yes, in summary, I did find a solution. The solution was provided by the Awesome Stephen Toub from Microsoft. He summarized the solution using an elegant construct called AsyncSemaphore and AsyncLock. Brilliant!
Here are the links:
http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266983.aspx
http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx
Here is the full code in case you don’t want to copy paste it step by step from the above:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | // http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266983.aspx public class AsyncSemaphore { private readonly static Task s_completed = Task.FromResult(true); private readonly Queue<TaskCompletionSource<bool>> m_waiters = new Queue<TaskCompletionSource<bool>>(); private int m_currentCount; public AsyncSemaphore(int initialCount) { if (initialCount < 0) throw new ArgumentOutOfRangeException("initialCount"); m_currentCount = initialCount; } public Task WaitAsync() { lock (m_waiters) { if (m_currentCount > 0) { --m_currentCount; return s_completed; } else { var waiter = new TaskCompletionSource<bool>(); m_waiters.Enqueue(waiter); return waiter.Task; } } } public void Release() { TaskCompletionSource<bool> toRelease = null; lock (m_waiters) { if (m_waiters.Count > 0) toRelease = m_waiters.Dequeue(); else ++m_currentCount; } if (toRelease != null) toRelease.SetResult(true); } } // http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx public class AsyncLock { private readonly AsyncSemaphore m_semaphore; private readonly Task<Releaser> m_releaser; public AsyncLock() { m_semaphore = new AsyncSemaphore(1); m_releaser = Task.FromResult(new Releaser(this)); } public Task<Releaser> LockAsync() { var wait = m_semaphore.WaitAsync(); return wait.IsCompleted ? m_releaser : wait.ContinueWith((_, state) => new Releaser((AsyncLock)state), this, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); } public struct Releaser : IDisposable { private readonly AsyncLock m_toRelease; internal Releaser(AsyncLock toRelease) { m_toRelease = toRelease; } public void Dispose() { if (m_toRelease != null) m_toRelease.m_semaphore.Release(); } } } |
The usage is pretty simple. This is how you use the AsyncLock
1 2 3 4 5 | AsyncLock myLock = new AsyncLock(); using(var releaser = await myLock.LockAsync()) { // do synchronized stuff here } |
Thank You Stephen Toub! You saved the day!
The whole reason we use async/await is asynchronous work, which doesn’t necessarily involve new threads.
This has solved a massive problem for me!
http://www.lumisoft.ee/Forum/Default.aspx?g=posts&t=14380&p=3&#
Thank you for this code. But I will use this in a portable class library or in a Silverlight project. Do you have a solution for this?
What about SemaphoreSlim instead of AsyncSemaphore ?
I now this is an old post… but hell it works. I wonder why this was never made a bit more obvious in the last few .Net releases.