Skip to content

Commit 3f6aeaf

Browse files
committed
Merge branch 'master' of https://github.com/rsdn/CodeJam
2 parents ce73100 + 0d11d12 commit 3f6aeaf

File tree

2 files changed

+79
-52
lines changed

2 files changed

+79
-52
lines changed

Main/src/Threading/AsyncLock.cs

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,15 @@ public class AsyncLock
2525
/// </param>
2626
/// <param name="cancellation">The CancellationToken token to observe.</param>
2727
/// <returns>A task that returns <see cref="IDisposable"/> to release the lock.</returns>
28-
[NotNull]
29-
[ItemNotNull]
30-
public async Task<IDisposable> Acquire(TimeSpan timeout, CancellationToken cancellation)
28+
[NotNull, ItemNotNull]
29+
public async Task<IDisposable> AcquireAsync(TimeSpan timeout, CancellationToken cancellation)
3130
{
32-
await _semaphore.WaitAsync(timeout, cancellation);
31+
var succeed = await _semaphore.WaitAsync(timeout, cancellation);
32+
if (!succeed)
33+
{
34+
cancellation.ThrowIfCancellationRequested();
35+
throw new TimeoutException($"Attempt to take lock timed out in {timeout}.");
36+
}
3337
return Disposable.Create(() => _semaphore.Release());
3438
}
3539

@@ -42,10 +46,9 @@ public async Task<IDisposable> Acquire(TimeSpan timeout, CancellationToken cance
4246
/// </param>
4347
/// <param name="cancellation">The CancellationToken token to observe.</param>
4448
/// <returns>A task that returns <see cref="IDisposable"/> to release the lock.</returns>
45-
[NotNull]
46-
[ItemNotNull]
47-
public Task<IDisposable> Acquire(int timeout, CancellationToken cancellation) =>
48-
Acquire(TimeSpan.FromMilliseconds(timeout), cancellation);
49+
[NotNull, ItemNotNull]
50+
public Task<IDisposable> AcquireAsync(int timeout, CancellationToken cancellation) =>
51+
AcquireAsync(TimeSpan.FromMilliseconds(timeout), cancellation);
4952

5053
/// <summary>
5154
/// Acquires async lock.
@@ -55,9 +58,8 @@ public Task<IDisposable> Acquire(int timeout, CancellationToken cancellation) =>
5558
/// indefinitely, or a 0 to return immediately.
5659
/// </param>
5760
/// <returns>A task that returns <see cref="IDisposable"/> to release the lock.</returns>
58-
[NotNull]
59-
[ItemNotNull]
60-
public Task<IDisposable> Acquire(int timeout) => Acquire(TimeSpan.FromMilliseconds(timeout), CancellationToken.None);
61+
[NotNull, ItemNotNull]
62+
public Task<IDisposable> AcquireAsync(int timeout) => AcquireAsync(TimeSpan.FromMilliseconds(timeout), CancellationToken.None);
6163

6264
/// <summary>
6365
/// Acquires async lock.
@@ -68,26 +70,23 @@ public Task<IDisposable> Acquire(int timeout, CancellationToken cancellation) =>
6870
/// to return immediately.
6971
/// </param>
7072
/// <returns>A task that returns <see cref="IDisposable"/> to release the lock.</returns>
71-
[NotNull]
72-
[ItemNotNull]
73-
public Task<IDisposable> Acquire(TimeSpan timeout) => Acquire(timeout, CancellationToken.None);
73+
[NotNull, ItemNotNull]
74+
public Task<IDisposable> AcquireAsync(TimeSpan timeout) => AcquireAsync(timeout, CancellationToken.None);
7475

7576
/// <summary>
7677
/// Acquires async lock.
7778
/// </summary>
7879
/// <param name="cancellation">The CancellationToken token to observe.</param>
7980
/// <returns>A task that returns <see cref="IDisposable"/> to release the lock.</returns>
80-
[NotNull]
81-
[ItemNotNull]
82-
public Task<IDisposable> Acquire(CancellationToken cancellation) => Acquire(-1, cancellation);
81+
[NotNull, ItemNotNull]
82+
public Task<IDisposable> AcquireAsync(CancellationToken cancellation) => AcquireAsync(-1, cancellation);
8383

8484
/// <summary>
8585
/// Acquires async lock.
8686
/// </summary>
8787
/// <returns>A task that returns <see cref="IDisposable"/> to release the lock.</returns>
88-
[NotNull]
89-
[ItemNotNull]
90-
public Task<IDisposable> Acquire() => Acquire(-1);
88+
[NotNull, ItemNotNull]
89+
public Task<IDisposable> AcquireAsync() => AcquireAsync(-1);
9190
}
9291
}
9392
#endif

Main/tests/Threading/AsyncLockTest.cs

Lines changed: 60 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,77 @@ namespace CodeJam.Threading
1212
[TestFixture]
1313
public class AsyncLockTest
1414
{
15+
private static async Task<bool> TryTakeAndHold(
16+
AsyncLock asyncLock, TimeSpan holdTime, CancellationToken cancellation = default(CancellationToken), Action callback = null)
17+
{
18+
try
19+
{
20+
using (await asyncLock.AcquireAsync(holdTime, cancellation))
21+
{
22+
callback?.Invoke();
23+
await Task.Delay(holdTime);
24+
}
25+
return true;
26+
}
27+
catch (OperationCanceledException)
28+
{
29+
return false;
30+
}
31+
catch (TimeoutException)
32+
{
33+
return false;
34+
}
35+
}
36+
37+
[Test]
38+
[SuppressMessage("ReSharper", "MethodSupportsCancellation")]
39+
public async Task LockCancellationTest()
40+
{
41+
var asyncLock = new AsyncLock();
42+
var holdTime = TimeSpan.FromSeconds(8);
43+
var delayTime = TimeSpan.FromMilliseconds(200);
44+
45+
var lock1Started = new ManualResetEventSlim(false);
46+
47+
var lock1 = TryTakeAndHold(asyncLock, holdTime, callback: () => lock1Started.Set());
48+
lock1Started.Wait();
49+
50+
var cts2 = new CancellationTokenSource();
51+
var sw2 = Stopwatch.StartNew();
52+
var lock2 = TryTakeAndHold(asyncLock, holdTime, cts2.Token);
53+
await Task.Delay(delayTime);
54+
cts2.Cancel();
55+
var lock2Taken = await lock2;
56+
sw2.Stop();
57+
58+
var sw3 = Stopwatch.StartNew();
59+
var lock3 = TryTakeAndHold(asyncLock, delayTime);
60+
await Task.Delay(delayTime);
61+
var lock3Taken = await lock3;
62+
sw3.Stop();
63+
64+
var lock1Taken = await lock1;
65+
66+
Assert.IsTrue(lock1Taken);
67+
Assert.IsFalse(lock2Taken);
68+
Assert.Less(sw2.Elapsed, holdTime - delayTime);
69+
Assert.IsFalse(lock3Taken);
70+
Assert.Less(sw3.Elapsed, holdTime - delayTime);
71+
}
72+
1573
[Test]
1674
public async Task LockTest()
1775
{
1876
var asyncLock = new AsyncLock();
77+
1978
var opActive = false;
2079
const int time = 200;
2180
const int timeInc = 10;
2281
const int count = 10;
2382

2483
async Task Op(int num)
2584
{
26-
using (await asyncLock.Acquire())
85+
using (await asyncLock.AcquireAsync())
2786
{
2887
Assert.IsFalse(opActive);
2988
opActive = true;
@@ -42,36 +101,5 @@ await Enumerable
42101
Assert.IsFalse(opActive);
43102
Assert.GreaterOrEqual(sw.ElapsedMilliseconds, time * count + timeInc * count / 2);
44103
}
45-
46-
[Test]
47-
[SuppressMessage("ReSharper", "MethodSupportsCancellation")]
48-
public async Task Cancellation()
49-
{
50-
var cts = new CancellationTokenSource();
51-
var lck = new AsyncLock();
52-
const int delay = 5000;
53-
const int ensureLockInterval = 30;
54-
Task.Run(
55-
async () =>
56-
{
57-
using (await lck.Acquire())
58-
await Task.Delay(delay);
59-
});
60-
await Task.Delay(ensureLockInterval);
61-
var sw = Stopwatch.StartNew();
62-
var task = Task.Run(
63-
async () =>
64-
{
65-
using (await lck.Acquire(cts.Token))
66-
sw.Stop();
67-
});
68-
cts.Cancel();
69-
try
70-
{
71-
await task;
72-
}
73-
catch (TaskCanceledException) {}
74-
Assert.Less(sw.ElapsedMilliseconds, delay - ensureLockInterval);
75-
}
76104
}
77105
}

0 commit comments

Comments
 (0)