Rx Challenge #11
This challenge is practice of building something already exists, by your own.
The challenge is to build async lock similar to the functionality of SemaphoreSlim WaitAsync / Release.
You can check this post for the functionality of SemaphoreSlim.
You can consider the following building block for this challenge:
– Concurrent Collection
– Interlocked
– TaskCompletionSource
You can validate your solution against this Test
- [TestMethod]
- public async Task AsyncLock_Test()
- {
- // arrange
- const int LIMIT = 3;
- var gate = new TaskCompletionSource<object>();
- var barrier = new CountdownEvent(LIMIT);
- var asyncLock = new AsyncLock(LIMIT);
- var timeout = TimeSpan.FromMinutes(1);
- int aquires = 0;
- // Act
- var query = from i in Enumerable.Range(0, LIMIT * 2)
- select Task.Run(async () =>
- {
- try
- {
- await asyncLock.WaitAsync();
- Interlocked.Increment(ref aquires);
- barrier.Signal();
- await gate.Task; // doesn't finish until the gate is open
- }
- finally
- {
- asyncLock.Release();
- }
- });
- var tasks = query.ToArray();
- // validate
- Assert.IsTrue(barrier.Wait(timeout)); // wait for the limites tread to be completed
- await Task.Yield(); // give other thread chance to pass the async lock (won't happens)
- Assert.AreEqual(LIMIT, aquires);
- barrier.Reset();
- gate.TrySetResult(null); // release the waiting threads
- Assert.IsTrue(barrier.Wait(timeout)); // wait for the limites tread to be completed
- Assert.AreEqual(LIMIT * 2, aquires);
- }
AsyncLock at line 8 is the class provides the solution.
The next step is to wrap your solution with Disposable in order to get better usability and replace the try finally block with using block.
You can validate the next step with the following test:
- [TestMethod]
- public async Task AsyncLocker_Test()
- {
- // arrange
- const int LIMIT = 3;
- var gate = new TaskCompletionSource<object>();
- var barrier = new CountdownEvent(LIMIT);
- var asyncLocker = new AsyncLocker(LIMIT);
- var timeout = TimeSpan.FromMinutes(1);
- int aquires = 0;
- // Act
- var query = from i in Enumerable.Range(0, LIMIT * 2)
- select Task.Run(async () =>
- {
- using (await asyncLocker.Acquire())
- {
- Interlocked.Increment(ref aquires);
- barrier.Signal();
- await gate.Task; // doesn't finish until the gate is open
- }
- });
- var tasks = query.ToArray();
- // validate
- Assert.IsTrue(barrier.Wait(timeout)); // wait for the limites tread to be completed
- await Task.Yield(); // give other thread chance to pass the async lock (won't happens)
- Assert.AreEqual(LIMIT, aquires);
- barrier.Reset();
- gate.TrySetResult(null); // release the waiting threads
- Assert.IsTrue(barrier.Wait(timeout)); // wait for the limites tread to be completed
- Assert.AreEqual(LIMIT * 2, aquires);
- }
AsyncLocker represent the Disposable wrapping provider over the previous solution (line 8).
It take effect at line 16.
All challenges available here.