Skip to content

Instantly share code, notes, and snippets.

@BrunoVT1992
Last active May 13, 2016 13:28
Show Gist options
  • Save BrunoVT1992/99f80cce65c636b7030cad9a25b53055 to your computer and use it in GitHub Desktop.
Save BrunoVT1992/99f80cce65c636b7030cad9a25b53055 to your computer and use it in GitHub Desktop.
This class makes a lock statement async so you can use await operators inside the lock statement
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace CSharp
{
// http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx
public class AsyncLock
{
private readonly AsyncSemaphore _mSemaphore;
private readonly Task<Releaser> _mReleaser;
public AsyncLock()
{
_mSemaphore = new AsyncSemaphore(1);
_mReleaser = Task.FromResult(new Releaser(this));
}
public Task<Releaser> LockAsync()
{
var wait = _mSemaphore.WaitAsync();
return wait.IsCompleted ?
_mReleaser :
wait.ContinueWith((_, state) => new Releaser((AsyncLock) state),
this, CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}
public struct Releaser : IDisposable
{
private readonly AsyncLock _mToRelease;
internal Releaser(AsyncLock toRelease)
{
_mToRelease = toRelease;
}
public void Dispose()
{
_mToRelease?._mSemaphore.Release();
}
}
// http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266983.aspx
public class AsyncSemaphore
{
private readonly static Task SCompleted = Task.FromResult(true);
private readonly Queue<TaskCompletionSource<bool>> _mWaiters = new Queue<TaskCompletionSource<bool>>();
private int _mCurrentCount;
public AsyncSemaphore(int initialCount)
{
if (initialCount < 0) throw new ArgumentOutOfRangeException(nameof(initialCount));
_mCurrentCount = initialCount;
}
public Task WaitAsync()
{
lock (_mWaiters)
{
if (_mCurrentCount > 0)
{
--_mCurrentCount;
return SCompleted;
}
var waiter = new TaskCompletionSource<bool>();
_mWaiters.Enqueue(waiter);
return waiter.Task;
}
}
public void Release()
{
TaskCompletionSource<bool> toRelease = null;
lock (_mWaiters)
{
if (_mWaiters.Count > 0)
{
toRelease = _mWaiters.Dequeue();
}
else
{
++_mCurrentCount;
}
}
toRelease?.SetResult(true);
}
}
public class Usage
{
public async Task Run()
{
var myLock = new AsyncLock();
using (var releaser = await myLock.LockAsync())
{
// do synchronized stuff here
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment