This repository was archived by the owner on Jun 11, 2021. It is now read-only.
forked from dotnet/dotNext
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathAsyncLock.cs
370 lines (334 loc) · 17.5 KB
/
AsyncLock.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using static System.Runtime.CompilerServices.Unsafe;
using static System.Threading.Timeout;
namespace DotNext.Threading
{
using Generic;
using Tasks;
/// <summary>
/// Unified representation of asynchronous exclusive lock, semaphore lock, read lock, write lock or upgradeable read lock.
/// </summary>
/// <remarks>
/// Lock acquisition is asynchronous operation. Note that non-blocking asynchronous locks are not intersected with
/// their blocking alternatives except semaphore. It means that exclusive lock obtained in blocking manner doesn't
/// prevent acquisition of asynchronous lock obtained in non-blocking manner.
/// </remarks>
/// <seealso cref="Lock"/>
[StructLayout(LayoutKind.Auto)]
public struct AsyncLock : IDisposable, IEquatable<AsyncLock>, IAsyncDisposable
{
internal enum Type : byte
{
None = 0,
Exclusive,
ReadLock,
UpgradeableReadLock,
WriteLock,
Semaphore,
Weak,
Strong,
}
/// <summary>
/// Represents acquired asynchronous lock.
/// </summary>
/// <remarks>
/// The lock can be released by calling <see cref="Dispose()"/>.
/// </remarks>
[StructLayout(LayoutKind.Auto)]
public struct Holder : IDisposable
{
private readonly object lockedObject;
private readonly Type type;
internal Holder(object lockedObject, Type type)
{
this.lockedObject = lockedObject;
this.type = type;
}
/// <summary>
/// Gets a value indicating that this object doesn't hold the lock.
/// </summary>
public readonly bool IsEmpty => lockedObject is null;
/// <summary>
/// Releases the acquired lock.
/// </summary>
/// <remarks>
/// This object is not reusable after calling of this method.
/// </remarks>
public void Dispose()
{
switch (type)
{
case Type.Exclusive:
As<AsyncExclusiveLock>(lockedObject).Release();
break;
case Type.ReadLock:
As<AsyncReaderWriterLock>(lockedObject).ExitReadLock();
break;
case Type.WriteLock:
As<AsyncReaderWriterLock>(lockedObject).ExitWriteLock();
break;
case Type.UpgradeableReadLock:
As<AsyncReaderWriterLock>(lockedObject).ExitUpgradeableReadLock();
break;
case Type.Semaphore:
As<SemaphoreSlim>(lockedObject).Release(1);
break;
case Type.Strong:
case Type.Weak:
As<AsyncSharedLock>(lockedObject).Release();
break;
}
this = default;
}
/// <summary>
/// Indicates that the object holds successfully acquired lock.
/// </summary>
/// <param name="holder">The lock holder.</param>
/// <returns><see langword="true"/>, if the object holds successfully acquired lock; otherwise, <see langword="false"/>.</returns>
public static implicit operator bool(in Holder holder) => holder.lockedObject is not null;
}
private readonly object lockedObject;
private readonly Type type;
private readonly bool owner;
private AsyncLock(object lockedObject, Type type, bool owner)
{
this.lockedObject = lockedObject;
this.type = type;
this.owner = owner;
}
private readonly Holder CreateHolder() => new(lockedObject, type);
/// <summary>
/// Creates exclusive asynchronous lock but doesn't acquire it.
/// </summary>
/// <remarks>
/// Constructed lock owns the exclusive lock instance.
/// </remarks>
/// <returns>Exclusive asynchronous lock.</returns>
/// <seealso cref="AsyncExclusiveLock"/>
public static AsyncLock Exclusive() => new(new AsyncExclusiveLock(), Type.Exclusive, true);
/// <summary>
/// Wraps exclusive lock into the unified representation of asynchronous lock.
/// </summary>
/// <param name="lock">The lock object to be wrapped.</param>
/// <returns>Exclusive asynchronous lock.</returns>
public static AsyncLock Exclusive(AsyncExclusiveLock @lock) => new(@lock ?? throw new ArgumentNullException(nameof(@lock)), Type.Exclusive, false);
/// <summary>
/// Wraps semaphore instance into the unified representation of the lock.
/// </summary>
/// <param name="semaphore">The semaphore to wrap into lock object.</param>
/// <returns>The lock representing semaphore.</returns>
public static AsyncLock Semaphore(SemaphoreSlim semaphore) => new(semaphore ?? throw new ArgumentNullException(nameof(semaphore)), Type.Semaphore, false);
/// <summary>
/// Creates semaphore-based lock but doesn't acquire the lock.
/// </summary>
/// <remarks>
/// Constructed lock owns the semaphore instance.
/// </remarks>
/// <param name="initialCount">The initial number of requests for the semaphore that can be granted concurrently.</param>
/// <param name="maxCount">The maximum number of requests for the semaphore that can be granted concurrently.</param>
/// <returns>The lock representing semaphore.</returns>
public static AsyncLock Semaphore(int initialCount, int maxCount) => new(new SemaphoreSlim(initialCount, maxCount), Type.Semaphore, true);
/// <summary>
/// Creates read lock but doesn't acquire it.
/// </summary>
/// <param name="rwLock">Read/write lock source.</param>
/// <param name="upgradeable"><see langword="true"/> to create upgradeable read lock wrapper.</param>
/// <returns>Reader lock.</returns>
public static AsyncLock ReadLock(AsyncReaderWriterLock rwLock, bool upgradeable)
=> new(rwLock ?? throw new ArgumentNullException(nameof(rwLock)), upgradeable ? Type.UpgradeableReadLock : Type.ReadLock, false);
/// <summary>
/// Creates write lock but doesn't acquire it.
/// </summary>
/// <param name="rwLock">Read/write lock source.</param>
/// <returns>Write-only lock.</returns>
public static AsyncLock WriteLock(AsyncReaderWriterLock rwLock)
=> new(rwLock ?? throw new ArgumentNullException(nameof(rwLock)), Type.WriteLock, false);
/// <summary>
/// Creates strong (exclusive) lock but doesn't acquire it.
/// </summary>
/// <param name="lock">The shared lock instance.</param>
/// <returns>Exclusive lock.</returns>
public static AsyncLock Exclusive(AsyncSharedLock @lock) => new(@lock, Type.Strong, false);
/// <summary>
/// Creates weak lock but doesn't acquire it.
/// </summary>
/// <param name="lock">The shared lock instance.</param>
/// <returns>Weak lock.</returns>
public static AsyncLock Weak(AsyncSharedLock @lock) => new(@lock, Type.Weak, false);
/// <summary>
/// Acquires the lock asynchronously.
/// </summary>
/// <param name="token">The token that can be used to abort acquisition operation.</param>
/// <returns>The task returning the acquired lock holder.</returns>
/// <exception cref="OperationCanceledException">The operation has been canceled.</exception>
public readonly Task<Holder> AcquireAsync(CancellationToken token) => AcquireAsync(InfiniteTimeSpan, token);
/// <summary>
/// Acquires the lock asynchronously.
/// </summary>
/// <param name="timeout">The interval to wait for the lock.</param>
/// <param name="token">The token that can be used to abort acquisition operation.</param>
/// <returns>The task returning the acquired lock holder.</returns>
/// <exception cref="TimeoutException">The lock cannot be acquired during the specified amount of time.</exception>
/// <exception cref="OperationCanceledException">The operation has been canceled.</exception>
public readonly async Task<Holder> AcquireAsync(TimeSpan timeout, CancellationToken token = default)
{
Task task;
switch (type)
{
default:
return default;
case Type.Exclusive:
task = As<AsyncExclusiveLock>(lockedObject).AcquireAsync(timeout, token);
break;
case Type.ReadLock:
task = As<AsyncReaderWriterLock>(lockedObject).EnterReadLockAsync(timeout, token);
break;
case Type.UpgradeableReadLock:
task = As<AsyncReaderWriterLock>(lockedObject).EnterUpgradeableReadLockAsync(timeout, token);
break;
case Type.WriteLock:
task = As<AsyncReaderWriterLock>(lockedObject).EnterWriteLockAsync(timeout, token);
break;
case Type.Semaphore:
task = As<SemaphoreSlim>(lockedObject).WaitAsync(timeout, token).CheckOnTimeout();
break;
case Type.Strong:
task = As<AsyncSharedLock>(lockedObject).AcquireAsync(true, timeout, token);
break;
case Type.Weak:
task = As<AsyncSharedLock>(lockedObject).AcquireAsync(false, timeout, token);
break;
}
await task.ConfigureAwait(false);
return CreateHolder();
}
/// <summary>
/// Tries to acquire the lock asynchronously.
/// </summary>
/// <param name="timeout">The interval to wait for the lock.</param>
/// <param name="suppressCancellation"><see langword="true"/> to return empty lock holder instead of throwing <see cref="OperationCanceledException"/>.</param>
/// <param name="token">The token that can be used to abort acquisition operation.</param>
/// <returns>The task returning the acquired lock holder; or empty lock holder if lock has not been acquired.</returns>
/// <exception cref="OperationCanceledException">The operation has been canceled and <paramref name="suppressCancellation"/> is <see langword="false"/>.</exception>
[Obsolete("Use SuppressCancellation() extension method")]
public readonly async Task<Holder> TryAcquireAsync(TimeSpan timeout, bool suppressCancellation, CancellationToken token)
{
var task = TryAcquireCoreAsync(timeout, token);
if (suppressCancellation && token.CanBeCanceled)
task = task.OnCanceled<bool, BooleanConst.False>();
return await task.ConfigureAwait(false) ? CreateHolder() : default;
}
private readonly Task<bool> TryAcquireCoreAsync(TimeSpan timeout, CancellationToken token) => type switch
{
Type.Exclusive => As<AsyncExclusiveLock>(lockedObject).TryAcquireAsync(timeout, token),
Type.ReadLock => As<AsyncReaderWriterLock>(lockedObject).TryEnterReadLockAsync(timeout, token),
Type.UpgradeableReadLock => As<AsyncReaderWriterLock>(lockedObject).TryEnterUpgradeableReadLockAsync(timeout, token),
Type.WriteLock => As<AsyncReaderWriterLock>(lockedObject).TryEnterWriteLockAsync(timeout, token),
Type.Semaphore => As<SemaphoreSlim>(lockedObject).WaitAsync(timeout, token),
Type.Strong => As<AsyncSharedLock>(lockedObject).TryAcquireAsync(true, timeout, token),
Type.Weak => As<AsyncSharedLock>(lockedObject).TryAcquireAsync(false, timeout, token),
_ => CompletedTask<bool, BooleanConst.False>.Task,
};
/// <summary>
/// Tries to acquire the lock asynchronously.
/// </summary>
/// <param name="timeout">The interval to wait for the lock.</param>
/// <param name="token">The token that can be used to abort acquisition operation.</param>
/// <returns>The task returning the acquired lock holder; or empty lock holder if lock has not been acquired.</returns>
/// <exception cref="OperationCanceledException">The operation has been canceled.</exception>
public readonly async Task<Holder> TryAcquireAsync(TimeSpan timeout, CancellationToken token = default)
=> await TryAcquireCoreAsync(timeout, token).ConfigureAwait(false) ? CreateHolder() : default;
/// <summary>
/// Tries to acquire lock asynchronously.
/// </summary>
/// <param name="token">The token that can be used to abort acquisition operation.</param>
/// <returns>The task returning the acquired lock holder; or empty lock holder if operation was canceled.</returns>
public readonly Task<Holder> TryAcquireAsync(CancellationToken token)
=> TryAcquireAsync(InfiniteTimeSpan, token);
/// <summary>
/// Destroy this lock and dispose underlying lock object if it is owned by the given lock.
/// </summary>
/// <remarks>
/// If the given lock is an owner of the underlying lock object then this method will call <see cref="IDisposable.Dispose()"/> on it;
/// otherwise, the underlying lock object will not be destroyed.
/// As a result, this lock is not usable after calling of this method.
/// </remarks>
public void Dispose()
{
if (owner && lockedObject is IDisposable disposable)
disposable.Dispose();
this = default;
}
/// <summary>
/// Destroy this lock and asynchronously dispose underlying lock object if it is owned by the given lock.
/// </summary>
/// <remarks>
/// If the given lock is an owner of the underlying lock object then this method will
/// call <see cref="IAsyncDisposable.DisposeAsync"/> or <see cref="IDisposable.Dispose()"/> on it;
/// otherwise, the underlying lock object will not be destroyed.
/// As a result, this lock is not usable after calling of this method.
/// </remarks>
/// <returns>The task representing asynchronous execution of this method.</returns>
public ValueTask DisposeAsync()
{
ValueTask result = default;
if (owner)
{
switch (lockedObject)
{
case IAsyncDisposable disposable:
result = disposable.DisposeAsync();
break;
case IDisposable disposable:
disposable.Dispose();
break;
}
}
this = default;
return result;
}
private readonly bool Equals(in AsyncLock other)
=> type == other.type && ReferenceEquals(lockedObject, other.lockedObject) && owner == other.owner;
/// <summary>
/// Determines whether this lock object is the same as other lock.
/// </summary>
/// <param name="other">Other lock to compare.</param>
/// <returns><see langword="true"/> if this lock is the same as the specified lock; otherwise, <see langword="false"/>.</returns>
public readonly bool Equals(AsyncLock other) => Equals(in other);
/// <summary>
/// Determines whether this lock object is the same as other lock.
/// </summary>
/// <param name="other">Other lock to compare.</param>
/// <returns><see langword="true"/> if this lock is the same as the specified lock; otherwise, <see langword="false"/>.</returns>
public override readonly bool Equals(object? other) => other is AsyncLock @lock && Equals(in @lock);
/// <summary>
/// Computes hash code of this lock.
/// </summary>
/// <returns>The hash code of this lock.</returns>
public override readonly int GetHashCode() => HashCode.Combine(lockedObject, type, owner);
/// <summary>
/// Returns actual type of this lock in the form of the string.
/// </summary>
/// <returns>The actual type of this lock.</returns>
public override readonly string ToString() => type.ToString();
/// <summary>
/// Determines whether two locks are the same.
/// </summary>
/// <param name="first">The first lock to compare.</param>
/// <param name="second">The second lock to compare.</param>
/// <returns><see langword="true"/>, if both are the same; otherwise, <see langword="false"/>.</returns>
public static bool operator ==(in AsyncLock first, in AsyncLock second)
=> first.Equals(in second);
/// <summary>
/// Determines whether two locks are not the same.
/// </summary>
/// <param name="first">The first lock to compare.</param>
/// <param name="second">The second lock to compare.</param>
/// <returns><see langword="true"/>, if both are not the same; otherwise, <see langword="false"/>.</returns>
public static bool operator !=(in AsyncLock first, in AsyncLock second)
=> !first.Equals(in second);
}
}