About me

Michael L Perry

Improving Enterprises

Principal Consultant

@michaellperry

User login

Awaitable critical section

.NET 4.5 introduces the async and await keywords. In the very near future (Windows 8, WinRT), most API functions will be asynchronous. Code that you wrote using synchronous APIs will no longer work.

For example, suppose you used to write to a file like this:

   1: lock (this)
   2: {
   3:     FileHandle file = FileHandle.Open();
   4:     file.Write(value);
   5:     file.Close();
   6: }

Now you have to write to it using the asynchronous API. Just changing it to this won’t work:

   1: lock (this)
   2: {
   3:     FileHandle file = await FileHandle.OpenAsync();
   4:     await file.WriteAsync(value);
   5:     file.Close();
   6: }

This code fails to compile with the error “The 'await' operator cannot be used in the body of a lock statement”. So you get rid of the lock:

   1: // BAD CODE! Multiple writes allowed.
   2: FileHandle file = await FileHandle.OpenAsync();
   3: await file.WriteAsync(value);
   4: file.Close();

And now it works most of the time. But every once in a while, you get two writes to the same file. If you’re lucky, this will just result in an exception like “The process cannot access the file because it is being used by another process”. If you aren’t, this will corrupt your file.

The solution

So how do you protect a shared resource, such as a file, without blocking the thread? Enter the AwaitableCriticalSection.

The call to EnterAsync() will only return for one caller at a time. It returns an IDisposable, so once you leave the using block, it will allow the next caller to enter. Threads are not blocked, but only one caller can be in the critical section at a time.

   1: using (var section = await _criticalSection.EnterAsync())
   2: {
   3:     FileHandle file = await FileHandle.OpenAsync();
   4:     await file.WriteAsync(value);
   5:     file.Close();
   6: }

Download the source code and add it to your project. Feel free to change the namespace.

How it works

The AwaitableCriticalSection keeps a collection of Tokens. Each Token represents one call to EnterAsync(). A token implements IAsyncResult. It’s basically just a wrapper around a ManualResetEvent, which can be signaled when it’s your turn to enter the critical section.

Think of a Token as a “take-a-number” ticket, or a restaurant pager. AwaitableCriticalSection is the dispenser, or the hostess. When you call EnterAsync(), it dispenses a Token to you. At the same time, it places this token in a queue.

If there is no one else in the critical section at that moment, then your token is instantly signaled. It passes true to the Signal method so that CompletedSynchronously will be set. This causes await to continue without relinquishing the thread.

   1: public Task<IDisposable> EnterAsync()
   2: {
   3:     lock (this)
   4:     {
   5:         Token token = new Token();
   6:         _tokens.Enqueue(token);
   7:         if (!_busy)
   8:         {
   9:             _busy = true;
  10:             _tokens.Dequeue().Signal(true);
  11:         }
  12:         return Task.Factory.FromAsync(token, result => _disposable);
  13:     }
  14: }

Await will open up the async result, calling the lambda expression that returns _disposable. This is simply an object that calls Exit when you leave the using block.

   1: private void Exit()
   2: {
   3:     lock (this)
   4:     {
   5:         if (_tokens.Any())
   6:         {
   7:             _tokens.Dequeue().Signal(false);
   8:         }
   9:         else
  10:         {
  11:             _busy = false;
  12:         }
  13:     }
  14: }

Exit signals the next token in line. This time, it passes false to let you know it didn’t complete synchronously. It had to wait. If there is nobody left in line, then it sets _busy to false. There is nobody in the critical section.

Locks block threads. New operating system APIs won’t allow you to block threads. So use an awaitable critical section to protect shared resources while maintaining a fast and fluid UI.