C# synchronization blocks containing await or how to lock a block with an await

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
Object lockObj = new object()
Lock(lockObj)
{
  Await fileIO
}

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!