Skip to content

Commit ef97135

Browse files
committed
Added UseWriterLockAsync to ReaderReleaser
1 parent 81f47de commit ef97135

File tree

10 files changed

+163
-41
lines changed

10 files changed

+163
-41
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,19 @@ using CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSec
3131
//acquire writer lock
3232
using var d2 = await asyncLock.WriterLockAsync("123", cts.Token);
3333

34+
//use writer lock scope
35+
36+
using var r1 = await asyncLock.ReaderLockAsync();
37+
38+
//use reader lock
39+
40+
if (..)
41+
{
42+
r1.UseWriterLockAsync(async () => { /*use writer lock here*/ });
43+
}
44+
45+
//continue with reader lock
46+
3447
```
3548

3649
### Benchmarks

src/AsyncKeyLock.Tests/AsyncLockTest.cs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,4 +174,62 @@ async Task enterReadThenWrite()
174174

175175
await Task.WhenAll(Enumerable.Range(0, 100).Select(x => Task.Run(enterReadThenWrite)));
176176
}
177+
178+
[Fact]
179+
public async Task ReaderReleaserUseWriterLock()
180+
{
181+
AsyncLock asyncLock = new AsyncLock();
182+
183+
using var r1 = await asyncLock.ReaderLockAsync();
184+
185+
await r1.UseWriterLockAsync(async () =>
186+
{
187+
Assert.Equal(0, asyncLock.CountRunningReaders);
188+
Assert.True(asyncLock.IsWriterRunning);
189+
});
190+
191+
Assert.Equal(1, asyncLock.CountRunningReaders);
192+
Assert.False(asyncLock.IsWriterRunning);
193+
}
194+
195+
[Fact]
196+
public async Task ReaderReleaserUseWriterLockWithCancellation()
197+
{
198+
AsyncLock asyncLock = new AsyncLock();
199+
200+
using var r1 = await asyncLock.ReaderLockAsync();
201+
202+
CancellationTokenSource cts = new CancellationTokenSource();
203+
cts.Cancel();
204+
205+
await Assert.ThrowsAsync<OperationCanceledException>(async () =>
206+
{
207+
await r1.UseWriterLockAsync(async () =>
208+
{
209+
throw new AccessViolationException();
210+
}, cts.Token);
211+
});
212+
213+
Assert.Equal(1, asyncLock.CountRunningReaders);
214+
Assert.False(asyncLock.IsWriterRunning);
215+
}
216+
217+
[Fact]
218+
public async Task ReaderReleaserUseWriterLockWithException()
219+
{
220+
AsyncLock asyncLock = new AsyncLock();
221+
222+
using var r1 = await asyncLock.ReaderLockAsync();
223+
224+
await Assert.ThrowsAsync<Exception>(async () =>
225+
{
226+
await r1.UseWriterLockAsync(async () =>
227+
{
228+
throw new Exception();
229+
});
230+
});
231+
232+
Assert.Equal(1, asyncLock.CountRunningReaders);
233+
Assert.False(asyncLock.IsWriterRunning);
234+
}
177235
}

src/AsyncKeyLock/AsyncLock.cs

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Copyright (c) usercode
2-
// https://github.com/usercode/AsyncLock
2+
// https://github.com/usercode/AsyncKeyLock
33
// MIT License
44

55
namespace AsyncKeyLock;
@@ -13,8 +13,8 @@ public AsyncLock()
1313
{
1414
_syncObj = _waitingWriters;
1515

16-
_readerReleaserTask = Task.FromResult(new AsyncLockReleaser(this, AsyncLockType.Read, true));
17-
_writerReleaserTask = Task.FromResult(new AsyncLockReleaser(this, AsyncLockType.Write, true));
16+
_readerReleaserTask = Task.FromResult(new ReaderReleaser(this, true));
17+
_writerReleaserTask = Task.FromResult(new WriterReleaser(this, true));
1818
}
1919

2020
internal AsyncLock(object syncObject)
@@ -25,13 +25,12 @@ internal AsyncLock(object syncObject)
2525

2626
private readonly object _syncObj;
2727

28-
private readonly Queue<TaskCompletionSource<AsyncLockReleaser>> _waitingReaders = new Queue<TaskCompletionSource<AsyncLockReleaser>>();
29-
private readonly Queue<TaskCompletionSource<AsyncLockReleaser>> _waitingWriters = new Queue<TaskCompletionSource<AsyncLockReleaser>>();
28+
private readonly Queue<TaskCompletionSource<ReaderReleaser>> _waitingReaders = new Queue<TaskCompletionSource<ReaderReleaser>>();
29+
private readonly Queue<TaskCompletionSource<WriterReleaser>> _waitingWriters = new Queue<TaskCompletionSource<WriterReleaser>>();
3030

31-
private readonly Task<AsyncLockReleaser> _readerReleaserTask;
32-
private readonly Task<AsyncLockReleaser> _writerReleaserTask;
31+
private readonly Task<ReaderReleaser> _readerReleaserTask;
32+
private readonly Task<WriterReleaser> _writerReleaserTask;
3333

34-
3534
private int _readersRunning;
3635

3736
private bool _isWriterRunning = false;
@@ -80,11 +79,11 @@ public AsyncLockState State
8079
}
8180
}
8281

83-
public Task<AsyncLockReleaser> ReaderLockAsync(CancellationToken cancellation = default)
82+
public Task<ReaderReleaser> ReaderLockAsync(CancellationToken cancellation = default)
8483
{
8584
if (cancellation.IsCancellationRequested)
8685
{
87-
return Task.FromCanceled<AsyncLockReleaser>(cancellation);
86+
return Task.FromCanceled<ReaderReleaser>(cancellation);
8887
}
8988

9089
lock (_syncObj)
@@ -93,47 +92,47 @@ public Task<AsyncLockReleaser> ReaderLockAsync(CancellationToken cancellation =
9392
if (_isWriterRunning == false && _waitingWriters.Count == 0)
9493
{
9594
_readersRunning++;
95+
9696
return _readerReleaserTask;
9797
}
9898
else
9999
{
100100
//create waiting reader
101-
TaskCompletionSource<AsyncLockReleaser> waiter = _waitingReaders.Enqueue(cancellation);
102-
return waiter.Task;
101+
return _waitingReaders.Enqueue(cancellation);
103102
}
104103
}
105104
}
106105

107-
public Task<AsyncLockReleaser> WriterLockAsync(CancellationToken cancellation = default)
106+
public Task<WriterReleaser> WriterLockAsync(CancellationToken cancellation = default)
108107
{
109108
if (cancellation.IsCancellationRequested)
110109
{
111-
return Task.FromCanceled<AsyncLockReleaser>(cancellation);
110+
return Task.FromCanceled<WriterReleaser>(cancellation);
112111
}
113112

114113
lock (_syncObj)
115114
{
116115
if (_isWriterRunning == false && _readersRunning == 0)
117116
{
118117
_isWriterRunning = true;
118+
119119
return _writerReleaserTask;
120120
}
121121
else
122122
{
123123
//create waiting writer
124-
TaskCompletionSource<AsyncLockReleaser> waiter = _waitingWriters.Enqueue(cancellation);
125-
return waiter.Task;
124+
return _waitingWriters.Enqueue(cancellation);
126125
}
127126
}
128127
}
129128

130-
internal void Release(AsyncLockReleaser releaser)
129+
internal void Release(AsyncLockType type)
131130
{
132131
lock (_syncObj)
133132
{
134133
try
135134
{
136-
if (releaser.Type == AsyncLockType.Write)
135+
if (type == AsyncLockType.Write)
137136
{
138137
WriterRelease();
139138
}
@@ -175,7 +174,7 @@ private void WriterRelease()
175174
{
176175
var taskSource = _waitingReaders.Dequeue();
177176

178-
bool result = taskSource.TrySetResult(new AsyncLockReleaser(this, AsyncLockType.Read, false));
177+
bool result = taskSource.TrySetResult(new ReaderReleaser(this, false));
179178

180179
if (result)
181180
{
@@ -191,7 +190,7 @@ private void StartNextWaitingWriter()
191190
{
192191
var taskSource = _waitingWriters.Dequeue();
193192

194-
bool result = taskSource.TrySetResult(new AsyncLockReleaser(this, AsyncLockType.Write, false));
193+
bool result = taskSource.TrySetResult(new WriterReleaser(this, false));
195194

196195
if (result == true)
197196
{

src/AsyncKeyLock/AsyncLockState.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Copyright (c) usercode
2-
// https://github.com/usercode/AsyncLock
2+
// https://github.com/usercode/AsyncKeyLock
33
// MIT License
44

55
namespace AsyncKeyLock;

src/AsyncKeyLock/AsyncLockType.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Copyright (c) usercode
2-
// https://github.com/usercode/AsyncLock
2+
// https://github.com/usercode/AsyncKeyLock
33
// MIT License
44

55
namespace AsyncKeyLock;

src/AsyncKeyLock/AsyncLock~.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Copyright (c) usercode
2-
// https://github.com/usercode/AsyncLock
2+
// https://github.com/usercode/AsyncKeyLock
33
// MIT License
44

55
namespace AsyncKeyLock;
@@ -54,7 +54,7 @@ private AsyncLock GetAsyncLock(TKey key)
5454
/// </summary>
5555
/// <param name="key"></param>
5656
/// <returns></returns>
57-
public Task<AsyncLockReleaser> ReaderLockAsync(TKey key, CancellationToken cancellation = default)
57+
public Task<ReaderReleaser> ReaderLockAsync(TKey key, CancellationToken cancellation = default)
5858
{
5959
lock (_locks)
6060
{
@@ -67,7 +67,7 @@ public Task<AsyncLockReleaser> ReaderLockAsync(TKey key, CancellationToken cance
6767
/// </summary>
6868
/// <param name="key"></param>
6969
/// <returns></returns>
70-
public Task<AsyncLockReleaser> WriterLockAsync(TKey key, CancellationToken cancellation = default)
70+
public Task<WriterReleaser> WriterLockAsync(TKey key, CancellationToken cancellation = default)
7171
{
7272
lock (_locks)
7373
{

src/AsyncKeyLock/Pool.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
namespace AsyncKeyLock;
1+
// Copyright (c) usercode
2+
// https://github.com/usercode/AsyncKeyLock
3+
// MIT License
4+
5+
namespace AsyncKeyLock;
26

37
/// <summary>
48
/// Pool

src/AsyncKeyLock/QueueExtensions.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
// Copyright (c) usercode
2-
// https://github.com/usercode/AsyncLock
2+
// https://github.com/usercode/AsyncKeyLock
33
// MIT License
44

55
namespace AsyncKeyLock;
66

77
public static class QueueExtensions
88
{
9-
public static TaskCompletionSource<T> Enqueue<T>(this Queue<TaskCompletionSource<T>> queue, CancellationToken cancellationToken)
9+
public static Task<T> Enqueue<T>(this Queue<TaskCompletionSource<T>> queue, CancellationToken cancellationToken)
1010
{
1111
TaskCompletionSource<T> item = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
1212

@@ -26,6 +26,6 @@ public static TaskCompletionSource<T> Enqueue<T>(this Queue<TaskCompletionSource
2626
CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
2727
}
2828

29-
return item;
29+
return item.Task;
3030
}
3131
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) usercode
2+
// https://github.com/usercode/AsyncKeyLock
3+
// MIT License
4+
5+
namespace AsyncKeyLock;
6+
7+
/// <summary>
8+
/// ReaderReleaser
9+
/// </summary>
10+
public readonly struct ReaderReleaser : IDisposable
11+
{
12+
/// <summary>
13+
/// AsyncLock
14+
/// </summary>
15+
private readonly AsyncLock AsyncLock;
16+
17+
/// <summary>
18+
/// IsAcquiredImmediately
19+
/// </summary>
20+
public readonly bool IsAcquiredImmediately;
21+
22+
internal ReaderReleaser(AsyncLock asyncLock, bool lockAcquiredImmediately)
23+
{
24+
AsyncLock = asyncLock;
25+
IsAcquiredImmediately = lockAcquiredImmediately;
26+
}
27+
28+
public async Task UseWriterLockAsync(Func<Task> func, CancellationToken cancellation = default)
29+
{
30+
cancellation.ThrowIfCancellationRequested();
31+
32+
//release reader lock
33+
AsyncLock.Release(AsyncLockType.Read);
34+
35+
try
36+
{
37+
//create new writer lock
38+
using (await AsyncLock.WriterLockAsync(cancellation))
39+
{
40+
await func();
41+
}
42+
}
43+
finally
44+
{
45+
//restore reader lock
46+
await AsyncLock.ReaderLockAsync();
47+
}
48+
}
49+
50+
public void Dispose()
51+
{
52+
AsyncLock.Release(AsyncLockType.Read);
53+
}
54+
}
Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,32 @@
11
// Copyright (c) usercode
2-
// https://github.com/usercode/AsyncLock
2+
// https://github.com/usercode/AsyncKeyLock
33
// MIT License
44

55
namespace AsyncKeyLock;
66

77
/// <summary>
8-
/// AsyncLockReleaser
8+
/// WriterReleaser
99
/// </summary>
10-
public readonly struct AsyncLockReleaser : IDisposable
10+
public readonly struct WriterReleaser : IDisposable
1111
{
1212
/// <summary>
1313
/// AsyncLock
1414
/// </summary>
1515
private readonly AsyncLock AsyncLock;
1616

17-
/// <summary>
18-
/// Type
19-
/// </summary>
20-
public readonly AsyncLockType Type;
21-
2217
/// <summary>
2318
/// IsAcquiredImmediately
2419
/// </summary>
2520
public readonly bool IsAcquiredImmediately;
2621

27-
internal AsyncLockReleaser(AsyncLock asyncLock, AsyncLockType type, bool lockAcquiredImmediately)
22+
internal WriterReleaser(AsyncLock asyncLock, bool lockAcquiredImmediately)
2823
{
2924
AsyncLock = asyncLock;
30-
Type = type;
3125
IsAcquiredImmediately = lockAcquiredImmediately;
32-
}
26+
}
3327

3428
public void Dispose()
3529
{
36-
AsyncLock.Release(this);
30+
AsyncLock.Release(AsyncLockType.Write);
3731
}
3832
}

0 commit comments

Comments
 (0)