├── .gitignore
├── .gitattributes
├── src
├── Directory.Build.props
├── AsyncUtilities
│ ├── AsyncUtilities.csproj.DotSettings
│ ├── Striped
│ │ ├── SimpleStriped.cs
│ │ ├── AsyncLock.cs
│ │ ├── StripedAsyncLock.cs
│ │ └── Striped.cs
│ ├── Extensions
│ │ ├── TaskExtensions.cs
│ │ ├── TaskCompletionSourceExtensions.cs
│ │ └── TaskExtensions.ContinueWithSynchronously.cs
│ ├── ValueTask
│ │ ├── ValueTaskAwaiter.cs
│ │ ├── ConfiguredValueTaskAwaitable.cs
│ │ ├── AsyncValueTaskMethodBuilder.cs
│ │ └── ValueTask.cs
│ ├── AsyncUtilities.csproj
│ ├── TaskEnumerableAwaiter
│ │ ├── TaskExtensions.cs
│ │ ├── ConfiguredTaskEnumerableAwaitable.cs
│ │ └── TaskEnumerableAwaiter.cs
│ └── CancelableTaskCompletionSource.cs
├── AsyncUtilities.sln
└── AsyncUtilities.sln.DotSettings
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .vs/
2 | [Dd]ebug/
3 | [Rr]elease/
4 | [Bb]in/
5 | [Oo]bj/
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
--------------------------------------------------------------------------------
/src/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | latest
4 | enable
5 | True
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/AsyncUtilities/AsyncUtilities.csproj.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | True
3 | True
4 | True
5 | True
--------------------------------------------------------------------------------
/src/AsyncUtilities.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26228.10
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsyncUtilities", "AsyncUtilities\AsyncUtilities.csproj", "{48FB83E3-E202-4A4D-9FCD-FDECF1C2F0FB}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {48FB83E3-E202-4A4D-9FCD-FDECF1C2F0FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {48FB83E3-E202-4A4D-9FCD-FDECF1C2F0FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {48FB83E3-E202-4A4D-9FCD-FDECF1C2F0FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {48FB83E3-E202-4A4D-9FCD-FDECF1C2F0FB}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | EndGlobal
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Bar Arnon
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/AsyncUtilities/Striped/SimpleStriped.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 |
5 | namespace AsyncUtilities
6 | {
7 | internal class SimpleStriped : Striped
8 | where TKey : notnull
9 | where TLock : class
10 | {
11 | private readonly TLock[] _locks;
12 |
13 | private ReadOnlyCollection? _locksReadOnlyWrapper;
14 |
15 | public override IEnumerable Locks =>
16 | _locksReadOnlyWrapper ??= new ReadOnlyCollection(_locks);
17 |
18 | public SimpleStriped(
19 | int stripes,
20 | Func creatorFunction,
21 | IEqualityComparer? comparer)
22 | : base(stripes, creatorFunction, comparer)
23 | {
24 | _locks = new TLock[_stripeMask + 1];
25 | for (var index = 0; index < _locks.Length; index++)
26 | {
27 | _locks[index] = _creatorFunction();
28 | }
29 | }
30 |
31 | protected override TLock GetLock(int stripe) =>
32 | _locks[stripe];
33 | }
34 | }
--------------------------------------------------------------------------------
/src/AsyncUtilities/Extensions/TaskExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace AsyncUtilities
6 | {
7 | /// Provides a set of static methods for working with specific kinds of
8 | /// instances.
9 | public static partial class TaskExtensions
10 | {
11 | ///
12 | /// Creates a that will signal
13 | /// cancellation when the provided will complete.
14 | ///
15 | /// The task that will trigger cancellation when it completes.
16 | ///
17 | /// A new instance that will signal cancellation
18 | /// to the s when the provided will be completed.
19 | ///
20 | ///
21 | /// The argument is null.
22 | ///
23 | public static CancellationTokenSource ToCancellationTokenSource(this Task task)
24 | {
25 | if (task is null) throw new ArgumentNullException(nameof(task));
26 |
27 | var cancellationTokenSource = new CancellationTokenSource();
28 | task.ContinueWithSynchronously(
29 | (_, state) => ((CancellationTokenSource)state!).Cancel(),
30 | cancellationTokenSource);
31 | return cancellationTokenSource;
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/src/AsyncUtilities/ValueTask/ValueTaskAwaiter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 |
4 | #if NETSTANDARD2_0
5 |
6 | namespace AsyncUtilities
7 | {
8 | /// Provides an awaiter for a .
9 | public readonly struct ValueTaskAwaiter : ICriticalNotifyCompletion
10 | {
11 | private readonly ValueTask _value;
12 |
13 | /// Gets whether the has completed.
14 | public bool IsCompleted =>
15 | _value.IsCompleted;
16 |
17 | /// Initializes the awaiter.
18 | /// The value to be awaited.
19 | internal ValueTaskAwaiter(ValueTask value) =>
20 | _value = value;
21 |
22 | /// Gets the result of the ValueTask.
23 | public void GetResult() =>
24 | _value._task?.GetAwaiter().GetResult();
25 |
26 | /// Schedules the continuation action for this ValueTask.
27 | public void OnCompleted(Action continuation) =>
28 | _value.
29 | AsTask().
30 | ConfigureAwait(continueOnCapturedContext: true).
31 | GetAwaiter().
32 | OnCompleted(continuation);
33 |
34 | /// Schedules the continuation action for this ValueTask.
35 | public void UnsafeOnCompleted(Action continuation) =>
36 | _value.
37 | AsTask().
38 | ConfigureAwait(continueOnCapturedContext: true).
39 | GetAwaiter().
40 | UnsafeOnCompleted(continuation);
41 | }
42 | }
43 |
44 | #endif
--------------------------------------------------------------------------------
/src/AsyncUtilities/AsyncUtilities.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Bar Arnon
5 | True
6 | A collection of somewhat useful utilities for async programming
7 | true
8 | true
9 | true
10 | true
11 | true
12 | true
13 | AsyncUtilities
14 | MIT
15 | README.md
16 | Update target frameworks, use latest C# features, add nullable annotations, add mising ConfigureAwaits, hide ValueTask.
17 | True
18 | async
19 | true
20 | snupkg
21 | netstandard2.0;net5.0
22 | 1.1.1
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/AsyncUtilities/Extensions/TaskCompletionSourceExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 | using static System.Threading.Tasks.TaskStatus;
4 |
5 | namespace AsyncUtilities
6 | {
7 | /// Provides a set of static methods for working with
8 | /// instances.
9 | public static class TaskCompletionSourceExtensions
10 | {
11 | ///
12 | /// Tries to complete a with the status
13 | /// and result of the provided .
14 | ///
15 | ///
16 | /// The type of the result value associated with this .
17 | ///
18 | ///
19 | /// The instance to complete with the .
20 | ///
21 | ///
22 | /// The completed to use when completing the .
23 | ///
24 | ///
25 | /// if the was completed successfully;
26 | /// otherwise, .
27 | ///
28 | public static bool TryCompleteFromCompletedTask(
29 | this TaskCompletionSource taskCompletionSource,
30 | Task completedTask)
31 | {
32 | if (taskCompletionSource is null) throw new ArgumentNullException(nameof(taskCompletionSource));
33 | if (completedTask is null) throw new ArgumentNullException(nameof(completedTask));
34 |
35 | return completedTask.Status switch
36 | {
37 | Faulted => taskCompletionSource.TrySetException(completedTask.Exception!.InnerExceptions),
38 | Canceled => taskCompletionSource.TrySetCanceled(),
39 | RanToCompletion => taskCompletionSource.TrySetResult(completedTask.Result),
40 | _ => throw new ArgumentException("Argument must be a completed task", nameof(completedTask))
41 | };
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/src/AsyncUtilities/TaskEnumerableAwaiter/TaskExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 |
5 | namespace AsyncUtilities
6 | {
7 | public static partial class TaskExtensions
8 | {
9 | /// Gets an awaiter for a collection of instances.
10 | /// The tasks to create an awaiter for.
11 | public static TaskEnumerableAwaiter GetAwaiter(this IEnumerable tasks)
12 | {
13 | if (tasks is null) throw new ArgumentNullException(nameof(tasks));
14 |
15 | return new TaskEnumerableAwaiter(tasks);
16 | }
17 |
18 | /// Configures an awaiter for a collection of instances.
19 | /// The tasks to create an awaiter for.
20 | ///
21 | /// true to attempt to marshal the continuation back to the captured context; otherwise, false.
22 | ///
23 | public static ConfiguredTaskEnumerableAwaitable ConfigureAwait(
24 | this IEnumerable tasks,
25 | bool continueOnCapturedContext)
26 | {
27 | if (tasks is null) throw new ArgumentNullException(nameof(tasks));
28 |
29 | return new ConfiguredTaskEnumerableAwaitable(tasks, continueOnCapturedContext);
30 | }
31 |
32 | /// Gets an awaiter for a collection of instances.
33 | /// The tasks to create an awaiter for.
34 | public static TaskEnumerableAwaiter GetAwaiter(this IEnumerable> tasks)
35 | {
36 | if (tasks is null) throw new ArgumentNullException(nameof(tasks));
37 |
38 | return new TaskEnumerableAwaiter(tasks);
39 | }
40 |
41 | /// Configures an awaiter for a collection of instances.
42 | /// The tasks to create an awaiter for.
43 | ///
44 | /// true to attempt to marshal the continuation back to the captured context; otherwise, false.
45 | ///
46 | public static ConfiguredTaskEnumerableAwaitable ConfigureAwait(
47 | this IEnumerable> tasks,
48 | bool continueOnCapturedContext)
49 | {
50 | if (tasks is null) throw new ArgumentNullException(nameof(tasks));
51 |
52 | return new ConfiguredTaskEnumerableAwaitable(tasks, continueOnCapturedContext);
53 | }
54 | }
55 | }
--------------------------------------------------------------------------------
/src/AsyncUtilities/TaskEnumerableAwaiter/ConfiguredTaskEnumerableAwaitable.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 |
5 | namespace AsyncUtilities
6 | {
7 | /// Provides an awaitable type that enables configured awaits on a collection of instances.
8 | public readonly struct ConfiguredTaskEnumerableAwaitable
9 | {
10 | private readonly IEnumerable _tasks;
11 | private readonly bool _continueOnCapturedContext;
12 |
13 | /// Initializes the awaitable.
14 | /// The tasks to be awaited.
15 | ///
16 | /// true to attempt to marshal the continuation back to the original synchronization context captured; otherwise, false.
17 | ///
18 | internal ConfiguredTaskEnumerableAwaitable(IEnumerable tasks, bool continueOnCapturedContext)
19 | {
20 | _tasks = tasks ?? throw new ArgumentNullException(nameof(tasks));
21 | _continueOnCapturedContext = continueOnCapturedContext;
22 | }
23 |
24 | /// Returns an awaiter for this instance.
25 | public TaskEnumerableAwaiter GetAwaiter() =>
26 | new TaskEnumerableAwaiter(_tasks, _continueOnCapturedContext);
27 | }
28 |
29 | /// Provides an awaitable type that enables configured awaits on a collection of instances.
30 | /// The type of the produced result.
31 | public readonly struct ConfiguredTaskEnumerableAwaitable
32 | {
33 | private readonly IEnumerable> _tasks;
34 | private readonly bool _continueOnCapturedContext;
35 |
36 | /// Initializes the awaitable.
37 | /// The tasks to be awaited.
38 | ///
39 | /// true to attempt to marshal the continuation back to the original synchronization context captured; otherwise, false.
40 | ///
41 | internal ConfiguredTaskEnumerableAwaitable(IEnumerable> tasks, bool continueOnCapturedContext)
42 | {
43 | _tasks = tasks ?? throw new ArgumentNullException(nameof(tasks));
44 | _continueOnCapturedContext = continueOnCapturedContext;
45 | }
46 |
47 | /// Returns an awaiter for this instance.
48 | public TaskEnumerableAwaiter GetAwaiter() =>
49 | new TaskEnumerableAwaiter(_tasks, _continueOnCapturedContext);
50 | }
51 | }
--------------------------------------------------------------------------------
/src/AsyncUtilities/Striped/AsyncLock.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | namespace AsyncUtilities
6 | {
7 | ///
8 | /// An asynchronous locking mechanism.
9 | ///
10 | public class AsyncLock
11 | {
12 | private readonly SemaphoreSlim _semaphore;
13 |
14 | ///
15 | /// Creates a new instance.
16 | ///
17 | public AsyncLock() =>
18 | _semaphore = new SemaphoreSlim(initialCount: 1, maxCount: 1);
19 |
20 | ///
21 | /// Asynchronously locks the .
22 | ///
23 | ///
24 | /// A task that will complete when the
25 | /// has been taken with a result. Disposing of the
26 | /// will release the .
27 | ///
28 | public ValueTask LockAsync() =>
29 | LockAsync(CancellationToken.None);
30 |
31 | ///
32 | /// Asynchronously locks the , while observing a
33 | /// .
34 | ///
35 | ///
36 | /// The token to observe.
37 | ///
38 | ///
39 | /// A task that will complete when the
40 | /// has been taken with a result. Disposing of the
41 | /// will release the .
42 | ///
43 | public async ValueTask LockAsync(CancellationToken cancellationToken)
44 | {
45 | await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
46 | return new Releaser(this);
47 | }
48 |
49 | ///
50 | /// enables holding an with a using scope.
51 | ///
52 | public struct Releaser : IDisposable
53 | {
54 | private readonly AsyncLock _asyncLock;
55 |
56 | private bool _isDisposed;
57 |
58 | internal Releaser(AsyncLock asyncLock)
59 | {
60 | _asyncLock = asyncLock;
61 | _isDisposed = false;
62 | }
63 |
64 | ///
65 | /// Releases the held .
66 | ///
67 | public void Dispose()
68 | {
69 | if (_isDisposed)
70 | {
71 | return;
72 | }
73 |
74 | _asyncLock?._semaphore.Release();
75 | _isDisposed = true;
76 | }
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/AsyncUtilities/ValueTask/ConfiguredValueTaskAwaitable.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 |
4 | #if NETSTANDARD2_0
5 |
6 | namespace AsyncUtilities
7 | {
8 | /// Provides an awaitable type that enables configured awaits on a .
9 | public readonly struct ConfiguredValueTaskAwaitable
10 | {
11 | private readonly ValueTask _value;
12 | private readonly bool _continueOnCapturedContext;
13 |
14 | /// Initializes the awaitable.
15 | /// The wrapped .
16 | ///
17 | /// true to attempt to marshal the continuation back to the original synchronization context captured; otherwise, false.
18 | ///
19 | internal ConfiguredValueTaskAwaitable(ValueTask value, bool continueOnCapturedContext)
20 | {
21 | _value = value;
22 | _continueOnCapturedContext = continueOnCapturedContext;
23 | }
24 |
25 | /// Returns an awaiter for this instance.
26 | public ConfiguredValueTaskAwaiter GetAwaiter() =>
27 | new ConfiguredValueTaskAwaiter(_value, _continueOnCapturedContext);
28 |
29 | /// Provides an awaiter for a .
30 | public readonly struct ConfiguredValueTaskAwaiter : ICriticalNotifyCompletion
31 | {
32 | private readonly ValueTask _value;
33 | private readonly bool _continueOnCapturedContext;
34 |
35 | /// Initializes the awaiter.
36 | /// The value to be awaited.
37 | /// The value to pass to ConfigureAwait.
38 | internal ConfiguredValueTaskAwaiter(ValueTask value, bool continueOnCapturedContext)
39 | {
40 | _value = value;
41 | _continueOnCapturedContext = continueOnCapturedContext;
42 | }
43 |
44 | /// Gets whether the has completed.
45 | public bool IsCompleted =>
46 | _value.IsCompleted;
47 |
48 | /// Gets the result of the ValueTask.
49 | public void GetResult() =>
50 | _value._task?.GetAwaiter().GetResult();
51 |
52 | /// Schedules the continuation action for the .
53 | public void OnCompleted(Action continuation) =>
54 | _value.
55 | AsTask().
56 | ConfigureAwait(_continueOnCapturedContext).
57 | GetAwaiter().
58 | OnCompleted(continuation);
59 |
60 | /// Schedules the continuation action for the .
61 | public void UnsafeOnCompleted(Action continuation) =>
62 | _value.
63 | AsTask().
64 | ConfigureAwait(_continueOnCapturedContext).
65 | GetAwaiter().
66 | UnsafeOnCompleted(continuation);
67 | }
68 | }
69 | }
70 |
71 | #endif
--------------------------------------------------------------------------------
/src/AsyncUtilities/TaskEnumerableAwaiter/TaskEnumerableAwaiter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Runtime.CompilerServices;
4 | using System.Threading.Tasks;
5 |
6 | namespace AsyncUtilities
7 | {
8 | /// Provides an awaiter for a collection of instances.
9 | public readonly struct TaskEnumerableAwaiter : ICriticalNotifyCompletion
10 | {
11 | private readonly ConfiguredTaskAwaitable.ConfiguredTaskAwaiter _awaiter;
12 |
13 | /// Gets whether the all the tasks completed.
14 | public bool IsCompleted => _awaiter.IsCompleted;
15 |
16 | /// Initializes the awaiter.
17 | /// The tasks to be awaited.
18 | ///
19 | /// true to attempt to marshal the continuation back to the original synchronization context captured; otherwise, false.
20 | ///
21 | internal TaskEnumerableAwaiter(IEnumerable tasks, bool continueOnCapturedContext = true)
22 | {
23 | if (tasks is null) throw new ArgumentNullException(nameof(tasks));
24 |
25 | _awaiter =
26 | Task.WhenAll(tasks).
27 | ConfigureAwait(continueOnCapturedContext).
28 | GetAwaiter();
29 | }
30 |
31 | /// Schedules the continuation action for these tasks.
32 | public void OnCompleted(Action continuation) =>
33 | _awaiter.OnCompleted(continuation);
34 |
35 | /// Schedules the continuation action for these tasks.
36 | public void UnsafeOnCompleted(Action continuation) =>
37 | _awaiter.UnsafeOnCompleted(continuation);
38 |
39 | /// Ends the await on the completed tasks.
40 | public void GetResult() =>
41 | _awaiter.GetResult();
42 | }
43 |
44 | /// Provides an awaiter for a collection of instances.
45 | /// The type of the produced result.
46 | public readonly struct TaskEnumerableAwaiter : ICriticalNotifyCompletion
47 | {
48 | private readonly ConfiguredTaskAwaitable.ConfiguredTaskAwaiter _awaiter;
49 |
50 | /// Gets whether the all the tasks completed.
51 | public bool IsCompleted => _awaiter.IsCompleted;
52 |
53 | /// Initializes the awaiter.
54 | /// The tasks to be awaited.
55 | ///
56 | /// true to attempt to marshal the continuation back to the original synchronization context captured; otherwise, false.
57 | ///
58 | internal TaskEnumerableAwaiter(IEnumerable> tasks, bool continueOnCapturedContext = true)
59 | {
60 | if (tasks is null) throw new ArgumentNullException(nameof(tasks));
61 |
62 | _awaiter =
63 | Task.WhenAll(tasks).
64 | ConfigureAwait(continueOnCapturedContext).
65 | GetAwaiter();
66 | }
67 |
68 | /// Schedules the continuation action for these tasks.
69 | public void OnCompleted(Action continuation) =>
70 | _awaiter.OnCompleted(continuation);
71 |
72 | /// Schedules the continuation action for these tasks.
73 | public void UnsafeOnCompleted(Action continuation) =>
74 | _awaiter.UnsafeOnCompleted(continuation);
75 |
76 | /// Ends the await on the completed tasks.
77 | public TResult[] GetResult() => _awaiter.GetResult();
78 | }
79 | }
--------------------------------------------------------------------------------
/src/AsyncUtilities/ValueTask/AsyncValueTaskMethodBuilder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 | using System.Security;
4 |
5 | #if NETSTANDARD2_0
6 |
7 | namespace AsyncUtilities
8 | {
9 | /// Represents a builder for asynchronous methods that returns a .
10 | public struct AsyncValueTaskMethodBuilder
11 | {
12 | private AsyncTaskMethodBuilder _methodBuilder;
13 | private bool _haveResult;
14 | private bool _useBuilder;
15 |
16 | /// Creates an instance of the struct.
17 | /// The initialized instance.
18 | public static AsyncValueTaskMethodBuilder Create() =>
19 | new AsyncValueTaskMethodBuilder
20 | {
21 | _methodBuilder = AsyncTaskMethodBuilder.Create()
22 | };
23 |
24 | /// Begins running the builder with the associated state machine.
25 | /// The type of the state machine.
26 | /// The state machine instance, passed by reference.
27 | public void Start(ref TStateMachine stateMachine)
28 | where TStateMachine : IAsyncStateMachine =>
29 | _methodBuilder.Start(ref stateMachine);
30 |
31 | /// Associates the builder with the specified state machine.
32 | /// The state machine instance to associate with the builder.
33 | public void SetStateMachine(IAsyncStateMachine stateMachine) =>
34 | _methodBuilder.SetStateMachine(stateMachine);
35 |
36 | /// Marks the task as successfully completed.
37 | public void SetResult()
38 | {
39 | if (_useBuilder)
40 | {
41 | _methodBuilder.SetResult();
42 | }
43 | else
44 | {
45 | _haveResult = true;
46 | }
47 | }
48 |
49 | /// Marks the task as failed and binds the specified exception to the task.
50 | /// The exception to bind to the task.
51 | public void SetException(Exception exception) =>
52 | _methodBuilder.SetException(exception);
53 |
54 | /// Gets the task for this builder.
55 | public ValueTask Task
56 | {
57 | get
58 | {
59 | if (_haveResult)
60 | {
61 | return new ValueTask();
62 | }
63 |
64 | _useBuilder = true;
65 | return new ValueTask(_methodBuilder.Task);
66 | }
67 | }
68 |
69 | /// Schedules the state machine to proceed to the next action when the specified awaiter completes.
70 | /// The type of the awaiter.
71 | /// The type of the state machine.
72 | /// the awaiter
73 | /// The state machine.
74 | public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine)
75 | where TAwaiter : INotifyCompletion
76 | where TStateMachine : IAsyncStateMachine
77 | {
78 | _useBuilder = true;
79 | _methodBuilder.AwaitOnCompleted(ref awaiter, ref stateMachine);
80 | }
81 |
82 | /// Schedules the state machine to proceed to the next action when the specified awaiter completes.
83 | /// The type of the awaiter.
84 | /// The type of the state machine.
85 | /// the awaiter
86 | /// The state machine.
87 | [SecuritySafeCritical]
88 | public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine)
89 | where TAwaiter : ICriticalNotifyCompletion
90 | where TStateMachine : IAsyncStateMachine
91 | {
92 | _useBuilder = true;
93 | _methodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
94 | }
95 | }
96 | }
97 |
98 | #endif
--------------------------------------------------------------------------------
/src/AsyncUtilities/CancelableTaskCompletionSource.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 | using static System.Threading.Tasks.TaskCreationOptions;
4 |
5 | namespace AsyncUtilities
6 | {
7 | ///
8 | /// Represents a associated with a .
9 | /// Canceling the will cancel the .
10 | ///
11 | ///
12 | /// The type of the result value associated with this .
13 | ///
14 | public class CancelableTaskCompletionSource : TaskCompletionSource
15 | {
16 | private readonly CancellationTokenRegistration _registration;
17 |
18 | ///
19 | /// Gets the associated with this .
20 | ///
21 | public CancellationToken CancellationToken { get; }
22 |
23 | ///
24 | /// Creates a .
25 | ///
26 | ///
27 | /// The to associate with the .
28 | ///
29 | public CancelableTaskCompletionSource(CancellationToken cancellationToken)
30 | : this(cancellationToken, state: null, None)
31 | {
32 | }
33 |
34 | ///
35 | /// Creates a .
36 | ///
37 | ///
38 | /// The to associate with the .
39 | ///
40 | ///
41 | /// The options to use when creating the underlying .
42 | ///
43 | public CancelableTaskCompletionSource(CancellationToken cancellationToken, TaskCreationOptions creationOptions)
44 | : this(cancellationToken, state: null, creationOptions)
45 | {
46 | }
47 |
48 |
49 | ///
50 | /// Creates a .
51 | ///
52 | ///
53 | /// The to associate with the .
54 | ///
55 | ///
56 | /// The state to use as the underlying 's AsyncState.
57 | ///
58 | public CancelableTaskCompletionSource(CancellationToken cancellationToken, object? state)
59 | : this(cancellationToken, state, None)
60 | {
61 | }
62 |
63 | ///
64 | /// Creates a .
65 | ///
66 | ///
67 | /// The to associate with the .
68 | ///
69 | ///
70 | /// The state to use as the underlying 's AsyncState.
71 | ///
72 | ///
73 | /// The options to use when creating the underlying .
74 | ///
75 | public CancelableTaskCompletionSource(
76 | CancellationToken cancellationToken,
77 | object? state,
78 | TaskCreationOptions creationOptions)
79 | : base(state, creationOptions)
80 | {
81 | CancellationToken = cancellationToken;
82 |
83 | if (!cancellationToken.CanBeCanceled)
84 | {
85 | return;
86 | }
87 |
88 | _registration = cancellationToken.Register(
89 | state => ((CancelableTaskCompletionSource)state!).TrySetCanceled(),
90 | this);
91 |
92 | Task.ContinueWithSynchronously(
93 | (_, state) => ((CancelableTaskCompletionSource)state!)._registration.Dispose(),
94 | this);
95 | }
96 | }
97 | }
--------------------------------------------------------------------------------
/src/AsyncUtilities.sln.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | DO_NOT_SHOW
3 | HINT
4 | DO_NOT_SHOW
5 | Named
6 | False
7 | True
8 | True
9 | True
10 | CHOP_IF_LONG
11 | True
12 | CHOP_IF_LONG
13 | CHOP_IF_LONG
14 | WRAP_IF_LONG
15 | CHOP_IF_LONG
16 | <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" />
17 | <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" />
18 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />
19 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
20 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
21 | C:\Users\Bar\AppData\Local\JetBrains\Transient\ReSharperPlatformVs15\v08_38529f48\SolutionCaches
22 | True
23 | True
24 | True
25 | True
26 | True
27 | True
--------------------------------------------------------------------------------
/src/AsyncUtilities/Striped/StripedAsyncLock.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | namespace AsyncUtilities
7 | {
8 | ///
9 | /// divides an into granular
10 | /// stripes allowing different operations to lock separate stripes concurrently instead
11 | /// of locking the entire altogether.
12 | ///
13 | ///
14 | /// The type of the keys the stripes correspond to.
15 | ///
16 | public class StripedAsyncLock where TKey : notnull
17 | {
18 | private readonly Striped _striped;
19 |
20 | ///
21 | /// Initializes a new instance with the
22 | /// specified number of stripes.
23 | ///
24 | ///
25 | /// The amount of stripes to divide the into.
26 | ///
27 | ///
28 | /// is less than 1.
29 | ///
30 | public StripedAsyncLock(int stripes)
31 | : this(stripes, comparer: null)
32 | {
33 | }
34 |
35 | ///
36 | /// Initializes a new instance with the
37 | /// specified number of stripes that uses the specified .
38 | ///
39 | ///
40 | /// The amount of stripes to divide the into.
41 | ///
42 | ///
43 | /// The implementation to use when generating
44 | /// a hash code in order to find a stripe.
45 | ///
46 | ///
47 | /// is less than 1.
48 | ///
49 | public StripedAsyncLock(int stripes, IEqualityComparer? comparer)
50 | {
51 | if (stripes <= 0) throw new ArgumentOutOfRangeException(nameof(stripes));
52 |
53 | _striped = Striped.Create(stripes, () => new AsyncLock(), comparer);
54 | }
55 |
56 | ///
57 | /// Asynchronously locks the .
58 | ///
59 | ///
60 | /// The key corresponding to the striped .
61 | ///
62 | ///
63 | /// A task that will complete when the
64 | /// has been taken with a result. Disposing of the
65 | /// will release the .
66 | ///
67 | ///
68 | /// is null.
69 | ///
70 | public ValueTask LockAsync(TKey key) =>
71 | LockAsync(key, CancellationToken.None);
72 |
73 | ///
74 | /// Asynchronously locks the , while observing a
75 | /// .
76 | ///
77 | ///
78 | /// The key corresponding to the striped .
79 | ///
80 | ///
81 | /// The token to observe.
82 | ///
83 | ///
84 | /// A task that will complete when the
85 | /// has been taken with a result. Disposing of the
86 | /// will release the .
87 | ///
88 | ///
89 | /// is null.
90 | ///
91 | public async ValueTask LockAsync(TKey key, CancellationToken cancellationToken)
92 | {
93 | if (key is null) throw new ArgumentNullException(nameof(key));
94 |
95 | return new Releaser(await _striped.GetLock(key).LockAsync(cancellationToken).ConfigureAwait(false));
96 | }
97 |
98 | ///
99 | /// enables holding an with a using scope.
100 | ///
101 | public struct Releaser : IDisposable
102 | {
103 | private AsyncLock.Releaser _releaser;
104 |
105 | internal Releaser(AsyncLock.Releaser releaser) =>
106 | _releaser = releaser;
107 |
108 | ///
109 | /// Releases the held .
110 | ///
111 | public void Dispose() =>
112 | _releaser.Dispose();
113 | }
114 | }
115 | }
--------------------------------------------------------------------------------
/src/AsyncUtilities/ValueTask/ValueTask.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 | using System.Threading.Tasks;
4 |
5 | #if NETSTANDARD2_0
6 |
7 | namespace AsyncUtilities
8 | {
9 | ///
10 | /// Provides a value type that wraps a .
11 | ///
12 | ///
13 | ///
14 | /// Methods may return an instance of this value type when it's likely that the result of their
15 | /// operations will be available synchronously and when the method is expected to be invoked so
16 | /// frequently that the cost of allocating a new for each call will
17 | /// be prohibitive.
18 | ///
19 | ///
20 | /// For example, for uses other than consuming the result of an asynchronous operation via await,
21 | /// can lead to a more convoluted programming model, which can in turn actually
22 | /// lead to more allocations. For example, consider a method that could return either a
23 | /// with a cached task as a common result or a . If the consumer of the result
24 | /// wants to use it as a , such as to use with in methods like Task.WhenAll and Task.WhenAny,
25 | /// the would first need to be converted into a using
26 | /// , which leads to an allocation that would have been avoided if a cached
27 | /// had been used in the first place.
28 | ///
29 | ///
30 | /// As such, the default choice for any asynchronous method should be to return a or
31 | /// . Only if performance analysis proves it worthwhile should a
32 | /// be used instead of .
33 | ///
34 | ///
35 | [AsyncMethodBuilder(typeof(AsyncValueTaskMethodBuilder))]
36 | public readonly struct ValueTask : IEquatable
37 | {
38 | private static readonly Task _completedTask = Task.FromResult(result: false);
39 |
40 | internal readonly Task _task;
41 |
42 | ///
43 | /// Initialize the with a that represents the operation.
44 | ///
45 | /// The task.
46 | public ValueTask(Task task) =>
47 | _task = task ?? throw new ArgumentNullException(nameof(task));
48 |
49 | /// Returns the hash code for this instance.
50 | public override int GetHashCode() =>
51 | _task?.GetHashCode() ?? 0;
52 |
53 | /// Returns a value indicating whether this value is equal to a specified .
54 | public override bool Equals(object obj) =>
55 | obj is ValueTask valueTask && Equals(valueTask);
56 |
57 | /// Returns a value indicating whether this value is equal to a specified value.
58 | public bool Equals(ValueTask other) =>
59 | _task == other._task;
60 |
61 | /// Returns a value indicating whether two values are equal.
62 | public static bool operator ==(ValueTask left, ValueTask right) =>
63 | left.Equals(right);
64 |
65 | /// Returns a value indicating whether two values are not equal.
66 | public static bool operator !=(ValueTask left, ValueTask right) =>
67 | !left.Equals(right);
68 |
69 | ///
70 | /// Gets a object to represent this ValueTask. It will
71 | /// either return the task object if one exists, or it'll manufacture a new
72 | /// task object to represent the result.
73 | ///
74 | public Task AsTask() =>
75 | _task ?? _completedTask;
76 |
77 | /// Gets whether the represents a completed operation.
78 | public bool IsCompleted =>
79 | _task is null || _task.IsCompleted;
80 |
81 | /// Gets whether the represents a successfully completed operation.
82 | public bool IsCompletedSuccessfully =>
83 | _task is null || _task.Status == TaskStatus.RanToCompletion;
84 |
85 | /// Gets whether the represents a failed operation.
86 | public bool IsFaulted =>
87 | _task is { IsFaulted: true };
88 |
89 | /// Gets whether the represents a canceled operation.
90 | public bool IsCanceled =>
91 | _task is { IsCanceled: true };
92 |
93 | /// Gets an awaiter for this value.
94 | public ValueTaskAwaiter GetAwaiter() =>
95 | new ValueTaskAwaiter(this);
96 |
97 | /// Configures an awaiter for this value.
98 | ///
99 | /// true to attempt to marshal the continuation back to the captured context; otherwise, false.
100 | ///
101 | public ConfiguredValueTaskAwaitable ConfigureAwait(bool continueOnCapturedContext) =>
102 | new ConfiguredValueTaskAwaitable(this, continueOnCapturedContext);
103 | }
104 | }
105 |
106 | #endif
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AsyncUtilities
2 | [](https://www.nuget.org/packages/AsyncUtilities)
3 | [](https://www.nuget.org/packages/AsyncUtilities)
4 | [](LICENSE)
5 |
6 | A collection of somewhat useful utilities and extension methods for async programming:
7 |
8 |
9 | [Utilities:](#utilities)
10 |
11 | 1. [AsyncLock](#async-lock)
12 | 1. [Striped Lock](#striped-lock)
13 | 1. [TaskEnumerableAwaiter](#task-enumerable-awaiter)
14 | 1. [CancelableTaskCompletionSource](#cancelable-task-completion-source)
15 |
16 | [Extension Methods:](#extension-methods)
17 |
18 | 1. [ContinueWithSynchronously](#continue-with-synchronously)
19 | 1. [TryCompleteFromCompletedTask](#complete-from-completed-task)
20 | 1. [ToCancellationTokenSource](#to-cancellation-token-source)
21 |
22 | ---
23 |
24 | ## `AsyncLock`
25 |
26 | When awaiting async operations, there's no thread-affinity (by default). It's common to start the operation in a certain thread, and resume in another. For that reason, all synchronization constructs that are thread-affine (i.e. match the lock with a specific thread) can't be used in an async context. One of these is the simple `lock` statement (which actually uses `Monitor.Enter`, `Monitor.Exit`). For such a case I included `AsyncLock` which is a simple wrapper over a `SemaphoreSlim` of 1 (for now, at least):
27 |
28 | ```csharp
29 | AsyncLock _lock = new AsyncLock();
30 |
31 | async Task ReplaceAsync(string id, T newItem)
32 | {
33 | using (var dbContext = new DBContext())
34 | {
35 | using (await _lock.LockAsync())
36 | {
37 | await dbContext.DeleteAsync(id);
38 | await dbContext.InsertAsync(newItem);
39 | }
40 | }
41 | }
42 | ```
43 |
44 | ---
45 |
46 | ## Striped Lock
47 |
48 | Lock striping is a technique used to reduce contention on a lock by splitting it up to multiple lock instances (*stripes*) with higher granularity where each key is associated with a certain lock/strip (this, for example, is how locks are used inside [`ConcurrentDictionary`](http://referencesource.microsoft.com/#mscorlib/system/Collections/Concurrent/ConcurrentDictionary.cs,f1cd2689b9df7f4a,references) to enable higher concurrency). Using a striped lock is similar in practice to using a `Dictionary` however that forces the number of locks to match the number of keys, while using a striped lock allows to set the concurrency level independently: A higher degree means more granularity but higher memory consumption and vice versa.
49 |
50 | ### `Striped`
51 |
52 | The `Striped` generic class allows using any kind of lock (with any kind of key). The only necessary things are:
53 |
54 | - The number of stripes
55 | - The lock must have a parameterless constructor, or a delegate for creating it is supplied.
56 | - The key must implement `GetHashCode` and `Equals` correctly, or an `IEqualityComparer` be supplied.
57 |
58 | ```csharp
59 | ThreadSafeCache _cache = new ThreadSafeCache();
60 | Striped _lock = Striped.Create(Environment.ProcessorCount);
61 |
62 | string GetContent(string filePath)
63 | {
64 | var content = _cache.Get(filePath);
65 | if (content != null)
66 | {
67 | return content;
68 | }
69 |
70 | lock (_lock[filePath])
71 | {
72 | // Double-checked lock
73 | content = _cache.Get(filePath);
74 | if (content != null)
75 | {
76 | return content;
77 | }
78 |
79 | content = File.ReadAllText(filePath);
80 | _cache.Set(content);
81 | return content;
82 | }
83 | }
84 | ```
85 |
86 | ### `StripedAsyncLock`
87 |
88 | Since this library is mainly for asynchronous programming, and you can't use a simple synchronous lock for async methods, there's also a concrete encapsulation for an async lock. It returns an `IDisposable` so it can be used in a `using` scope:
89 |
90 | ```csharp
91 | ThreadSafeCache _cache = new ThreadSafeCache();
92 | StripedAsyncLock _lock = new StripedAsyncLock(stripes: 100);
93 |
94 | async Task GetContentAsync(string filePath)
95 | {
96 | var content = _cache.Get(filePath);
97 | if (content != null)
98 | {
99 | return content;
100 | }
101 |
102 | using (await _lock.LockAsync(filePath))
103 | {
104 | // Double-checked lock
105 | content = _cache.Get(filePath);
106 | if (content != null)
107 | {
108 | return content;
109 | }
110 |
111 | using (var reader = File.OpenText(filePath))
112 | {
113 | content = await reader.ReadToEndAsync();
114 | }
115 |
116 | _cache.Set(content);
117 | return content;
118 | }
119 | }
120 | ```
121 |
122 | ---
123 |
124 | ## `TaskEnumerableAwaiter`
125 |
126 | `TaskEnumerableAwaiter` is an awaiter for a collection of tasks. It makes the C# compiler support awaiting a collection of tasks directly instead of calling `Task.WhenAll` first:
127 |
128 | ```csharp
129 | HttpClient _httpClient = new HttpClient();
130 |
131 | static async Task DownloadAllAsync()
132 | {
133 | string[] urls = new[]
134 | {
135 | "http://www.google.com",
136 | "http://www.github.com",
137 | "http://www.twitter.com"
138 | };
139 |
140 | string[] strings = await urls.Select(url => _httpClient.GetStringAsync(url));
141 | foreach (var content in strings)
142 | {
143 | Console.WriteLine(content);
144 | }
145 | }
146 | ```
147 |
148 | It supports both `IEnumerable` & `IEnumerable>` and using `ConfigureAwait(false)` to avoid context capturing. I've written about it more extensively [here](http://blog.i3arnon.com/2018/01/02/task-enumerable-awaiter/).
149 |
150 | ---
151 |
152 | ## `CancelableTaskCompletionSource`
153 |
154 | When you're implementing asynchronous operations yourself you're usually dealing with `TaskCompletionSource` which allows returning an uncompleted task and completing it in the future with a result, exception or cancellation. `CancelableTaskCompletionSource` joins together a `CancellationToken` and a `TaskCompletionSource` by cancelling the `CancelableTaskCompletionSource.Task` when the `CancellationToken` is cancelled. This can be useful when wrapping pre async/await custom asynchronous implementations, for example:
155 |
156 | ```csharp
157 | Task OperationAsync(CancellationToken cancellationToken)
158 | {
159 | var taskCompletionSource = new CancelableTaskCompletionSource(cancellationToken);
160 |
161 | StartAsynchronousOperation(result => taskCompletionSource.SetResult(result));
162 |
163 | return taskCompletionSource.Task;
164 | }
165 |
166 | void StartAsynchronousOperation(Action callback)
167 | {
168 | // ...
169 | }
170 | ```
171 |
172 | ---
173 |
174 | # Extension Methods:
175 |
176 | ## `TaskExtensions.ContinueWithSynchronously`
177 |
178 | When implementing low-level async constructs, it's common to add a small continuation using `Task.ContinueWith` instead of using an async method (which adds the state machine overhead). To do that efficiently and safely you need the `TaskContinuationOptions.ExecuteSynchronously` and make sure it runs on the `ThreadPool`:
179 |
180 | ```csharp
181 | Task.Delay(1000).ContinueWith(
182 | _ => Console.WriteLine("Done"),
183 | CancellationToken.None,
184 | TaskContinuationOptions.ExecuteSynchronously,
185 | TaskScheduler.Default);
186 | ```
187 |
188 | `TaskExtensions.ContinueWithSynchronously` encapsulates that for you (with all the possible overloads):
189 |
190 | ```csharp
191 | Task.Delay(1000).ContinueWithSynchronously(_ => Console.WriteLine("Done"));
192 | ```
193 |
194 | ---
195 |
196 | ## `TaskCompletionSourceExtensions.TryCompleteFromCompletedTask`
197 |
198 | When working with `TaskCompletionSource` it's common to copy the result (or exception/cancellation) of another task, usually returned from an async method. `TryCompleteFromCompletedTask` checks the task's state and complete the `TaskCompletionSource` accordingly. This can be used for example when there's a single worker executing the actual operations one at a time and completing `TaskCompletionSource` instances that consumers are awaiting:
199 |
200 | ```csharp
201 | BlockingCollection _urls = new BlockingCollection();
202 | Queue> _waiters = new Queue>();
203 | HttpClient _httpClient = new HttpClient();
204 |
205 | async Task DownloadAllAsync()
206 | {
207 | while (true)
208 | {
209 | await DownloadAsync(_urls.Take());
210 | }
211 | }
212 |
213 | async Task DownloadAsync(string url)
214 | {
215 | Task downloadTask = _httpClient.GetStringAsync(url);
216 | await downloadTask;
217 |
218 | TaskCompletionSource taskCompletionSource = _waiters.Dequeue();
219 | taskCompletionSource.TryCompleteFromCompletedTask(downloadTask);
220 | }
221 | ```
222 |
223 | ---
224 |
225 | ## `TaskExtensions.ToCancellationTokenSource`
226 |
227 | It can sometimes be useful to treat an existing task as a signaling mechanism for cancellation, especially when that task doesn't represent a specific operation but an ongoing state. `ToCancellationTokenSource` creates a `CancellationTokenSource` that gets cancelled when the task completes.
228 |
229 | For example, TPL Dataflow blocks have a `Completion` property which is a task to enable the block's consumer to await its completion. If we want to show a loading animation to represent the block's operation, we need to cancel it when the block completes and we know that happened when the `Completion` task completes:
230 |
231 | ```csharp
232 | HttpClient _httpClient = new HttpClient();
233 |
234 | async Task> DownloadAllAsync(IEnumerable urls)
235 | {
236 | var results = new ConcurrentBag();
237 | var block = new ActionBlock(async url =>
238 | {
239 | var result = await _httpClient.GetStringAsync(url);
240 | results.Add(result);
241 | });
242 |
243 | var cancellationToken = block.Completion.ToCancellationTokenSource().Token;
244 | var loadingAnimation = new LoadingAnimation(cancellationToken);
245 | loadingAnimation.Show();
246 |
247 | foreach (var url in urls)
248 | {
249 | block.Post(url);
250 | }
251 |
252 | await block.Completion;
253 | return results;
254 | }
255 | ```
256 |
257 | ---
258 |
--------------------------------------------------------------------------------
/src/AsyncUtilities/Extensions/TaskExtensions.ContinueWithSynchronously.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 | using static System.Threading.Tasks.TaskContinuationOptions;
5 |
6 | namespace AsyncUtilities
7 | {
8 | public static partial class TaskExtensions
9 | {
10 | ///
11 | /// Creates a continuation that executes synchronously on the ThreadPool when the target completes.
12 | ///
13 | ///
14 | /// The antecedent .
15 | ///
16 | ///
17 | /// An action to run when the completes. When run, the delegate will be
18 | /// passed the completed task as an argument.
19 | ///
20 | /// A new continuation .
21 | ///
22 | /// The argument is null.
23 | ///
24 | ///
25 | /// The argument is null.
26 | ///
27 | public static Task ContinueWithSynchronously(this Task task, Action continuationAction)
28 | {
29 | if (task is null) throw new ArgumentNullException(nameof(task));
30 | if (continuationAction is null) throw new ArgumentNullException(nameof(continuationAction));
31 |
32 | return task.ContinueWith(
33 | continuationAction,
34 | CancellationToken.None,
35 | ExecuteSynchronously,
36 | TaskScheduler.Default);
37 | }
38 |
39 | ///
40 | /// Creates a continuation that executes synchronously on the ThreadPool when the target completes.
41 | ///
42 | ///
43 | /// The antecedent .
44 | ///
45 | ///
46 | /// An action to run when the completes. When run, the delegate will be
47 | /// passed the completed task as an argument.
48 | ///
49 | ///
50 | /// An object representing data to be used by the continuation action.
51 | ///
52 | /// A new continuation .
53 | ///
54 | /// The argument is null.
55 | ///
56 | ///
57 | /// The argument is null.
58 | ///
59 | public static Task ContinueWithSynchronously(
60 | this Task task,
61 | Action continuationAction,
62 | object? state)
63 | {
64 | if (task is null) throw new ArgumentNullException(nameof(task));
65 | if (continuationAction is null) throw new ArgumentNullException(nameof(continuationAction));
66 |
67 | return task.ContinueWith(
68 | continuationAction,
69 | state,
70 | CancellationToken.None,
71 | ExecuteSynchronously,
72 | TaskScheduler.Default);
73 | }
74 |
75 | ///
76 | /// Creates a continuation that executes synchronously on the ThreadPool when the target completes.
77 | ///
78 | ///
79 | /// The antecedent .
80 | ///
81 | ///
82 | /// A function to run when the completes. When run, the delegate will be
83 | /// passed the completed task as an argument.
84 | ///
85 | /// A new continuation .
86 | ///
87 | /// The argument is null.
88 | ///
89 | ///
90 | /// The argument is null.
91 | ///
92 | public static Task ContinueWithSynchronously(
93 | this Task task,
94 | Func continuationFunction)
95 | {
96 | if (task is null) throw new ArgumentNullException(nameof(task));
97 | if (continuationFunction is null) throw new ArgumentNullException(nameof(continuationFunction));
98 |
99 | return task.ContinueWith(
100 | continuationFunction,
101 | CancellationToken.None,
102 | ExecuteSynchronously,
103 | TaskScheduler.Default);
104 | }
105 |
106 | ///
107 | /// Creates a continuation that executes synchronously on the ThreadPool when the target completes.
108 | ///
109 | ///
110 | /// The antecedent .
111 | ///
112 | ///
113 | /// A function to run when the completes. When run, the delegate will be
114 | /// passed the completed task as an argument.
115 | ///
116 | ///
117 | /// An object representing data to be used by the continuation function.
118 | ///
119 | /// A new continuation .
120 | ///
121 | /// The argument is null.
122 | ///
123 | ///
124 | /// The argument is null.
125 | ///
126 | public static Task ContinueWithSynchronously(
127 | this Task task,
128 | Func continuationFunction,
129 | object? state)
130 | {
131 | if (task is null) throw new ArgumentNullException(nameof(task));
132 | if (continuationFunction is null) throw new ArgumentNullException(nameof(continuationFunction));
133 |
134 | return task.ContinueWith(
135 | continuationFunction,
136 | state,
137 | CancellationToken.None,
138 | ExecuteSynchronously,
139 | TaskScheduler.Default);
140 | }
141 |
142 | ///
143 | /// Creates a continuation that executes synchronously on the ThreadPool when the target completes.
144 | ///
145 | ///
146 | /// The antecedent .
147 | ///
148 | ///
149 | /// An action to run when the completes. When run, the delegate will be
150 | /// passed the completed task as an argument.
151 | ///
152 | /// A new continuation .
153 | ///
154 | /// The argument is null.
155 | ///
156 | ///
157 | /// The argument is null.
158 | ///
159 | public static Task ContinueWithSynchronously(
160 | this Task task,
161 | Action> continuationAction)
162 | {
163 | if (task is null) throw new ArgumentNullException(nameof(task));
164 | if (continuationAction is null) throw new ArgumentNullException(nameof(continuationAction));
165 |
166 | return task.ContinueWith(
167 | continuationAction,
168 | CancellationToken.None,
169 | ExecuteSynchronously,
170 | TaskScheduler.Default);
171 | }
172 |
173 | ///
174 | /// Creates a continuation that executes synchronously on the ThreadPool when the target completes.
175 | ///
176 | ///
177 | /// The antecedent .
178 | ///
179 | ///
180 | /// An action to run when the completes. When run, the delegate will be
181 | /// passed the completed task as an argument.
182 | ///
183 | ///
184 | /// An object representing data to be used by the continuation action.
185 | ///
186 | /// A new continuation .
187 | ///
188 | /// The argument is null.
189 | ///
190 | ///
191 | /// The argument is null.
192 | ///
193 | public static Task ContinueWithSynchronously(
194 | this Task task,
195 | Action, object?> continuationAction,
196 | object? state)
197 | {
198 | if (task is null) throw new ArgumentNullException(nameof(task));
199 | if (continuationAction is null) throw new ArgumentNullException(nameof(continuationAction));
200 |
201 | return task.ContinueWith(
202 | continuationAction,
203 | state,
204 | CancellationToken.None,
205 | ExecuteSynchronously,
206 | TaskScheduler.Default);
207 | }
208 |
209 | ///
210 | /// Creates a continuation that executes synchronously on the ThreadPool when the target completes.
211 | ///
212 | ///
213 | /// The antecedent .
214 | ///
215 | ///
216 | /// A function to run when the completes. When run, the delegate will be
217 | /// passed the completed task as an argument.
218 | ///
219 | /// A new continuation .
220 | ///
221 | /// The argument is null.
222 | ///
223 | ///
224 | /// The argument is null.
225 | ///
226 | public static Task ContinueWithSynchronously(
227 | this Task task,
228 | Func, TNewResult> continuationFunction)
229 | {
230 | if (task is null) throw new ArgumentNullException(nameof(task));
231 | if (continuationFunction is null) throw new ArgumentNullException(nameof(continuationFunction));
232 |
233 | return task.ContinueWith(
234 | continuationFunction,
235 | CancellationToken.None,
236 | ExecuteSynchronously,
237 | TaskScheduler.Default);
238 | }
239 |
240 | ///
241 | /// Creates a continuation that executes synchronously on the ThreadPool when the target completes.
242 | ///
243 | ///
244 | /// The antecedent .
245 | ///
246 | ///
247 | /// A function to run when the completes. When run, the delegate will be
248 | /// passed the completed task as an argument.
249 | ///
250 | ///
251 | /// An object representing data to be used by the continuation function.
252 | ///
253 | /// A new continuation .
254 | ///
255 | /// The argument is null.
256 | ///
257 | ///
258 | /// The argument is null.
259 | ///
260 | public static Task ContinueWithSynchronously(
261 | this Task task,
262 | Func, object?, TNewResult> continuationFunction,
263 | object? state)
264 | {
265 | if (task is null) throw new ArgumentNullException(nameof(task));
266 | if (continuationFunction is null) throw new ArgumentNullException(nameof(continuationFunction));
267 |
268 | return task.ContinueWith(
269 | continuationFunction,
270 | state,
271 | CancellationToken.None,
272 | ExecuteSynchronously,
273 | TaskScheduler.Default);
274 | }
275 | }
276 | }
--------------------------------------------------------------------------------
/src/AsyncUtilities/Striped/Striped.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Runtime.CompilerServices;
4 |
5 | namespace AsyncUtilities
6 | {
7 | ///
8 | /// divides a locking mechanism into granular
9 | /// stripes allowing different operations to hold separate stripes concurrently instead
10 | /// of holding the entire locking mechanism altogether.
11 | ///
12 | public static class Striped
13 | {
14 | ///
15 | /// Create a new instance with the specified
16 | /// amount of stripes.
17 | ///
18 | ///
19 | /// The type of the keys the stripes correspond to.
20 | ///
21 | ///
22 | /// The type of the locking mechanism to stripe.
23 | ///
24 | ///
25 | /// The amount of stripes to divide the into.
26 | ///
27 | ///
28 | /// A new instance with the specified arguments.
29 | ///
30 | ///
31 | /// is less than 1.
32 | ///
33 | public static Striped Create(int stripes)
34 | where TKey : notnull
35 | where TLock : class, new() =>
36 | Create(stripes, () => new TLock());
37 |
38 | ///
39 | /// Create a new instance with the specified
40 | /// amount of stripes, that creates new locking mechanism instances using the
41 | /// creatorFunction.
42 | ///
43 | ///
44 | /// The type of the keys the stripes correspond to.
45 | ///
46 | ///
47 | /// The type of the locking mechanism to stripe.
48 | ///
49 | ///
50 | /// The amount of stripes to divide the into.
51 | ///
52 | ///
53 | /// The function to create new instances.
54 | ///
55 | ///
56 | /// A new instance with the specified arguments.
57 | ///
58 | ///
59 | /// is less than 1.
60 | ///
61 | ///
62 | /// is null.
63 | ///
64 | public static Striped Create(
65 | int stripes,
66 | Func creatorFunction)
67 | where TKey : notnull
68 | where TLock : class =>
69 | Create(stripes, creatorFunction, comparer: null);
70 |
71 | ///
72 | /// Create a new instance with the specified
73 | /// amount of stripes, that creates new locking mechanism instances using the
74 | /// creatorFunction and uses the specified .
75 | ///
76 | ///
77 | /// The type of the keys the stripes correspond to.
78 | ///
79 | ///
80 | /// The type of the locking mechanism to stripe.
81 | ///
82 | ///
83 | /// The amount of stripes to divide the into.
84 | ///
85 | ///
86 | /// The function to create new instances.
87 | ///
88 | ///
89 | /// The implementation to use when generating
90 | /// a hash code in order to find a stripe.
91 | ///
92 | ///
93 | /// A new instance with the specified arguments.
94 | ///
95 | ///
96 | /// is less than 1.
97 | ///
98 | ///
99 | /// is null.
100 | ///
101 | public static Striped Create(
102 | int stripes,
103 | Func creatorFunction,
104 | IEqualityComparer? comparer)
105 | where TKey : notnull
106 | where TLock : class
107 | {
108 | if (stripes <= 0) throw new ArgumentOutOfRangeException(nameof(stripes));
109 | if (creatorFunction is null) throw new ArgumentNullException(nameof(creatorFunction));
110 |
111 | return new SimpleStriped(stripes, creatorFunction, comparer);
112 | }
113 | }
114 |
115 | ///
116 | /// divides a into granular
117 | /// stripes allowing different operations to hold separate stripes concurrently instead
118 | /// of holding the entire altogether.
119 | ///
120 | ///
121 | /// The type of the keys the stripes correspond to.
122 | ///
123 | ///
124 | /// The type of the locking mechanism to stripe.
125 | ///
126 | public abstract class Striped
127 | where TKey : notnull
128 | where TLock : class
129 | {
130 | private readonly IEqualityComparer _comparer;
131 |
132 | ///
133 | /// The function to create new instances.
134 | ///
135 | protected readonly Func _creatorFunction;
136 |
137 | ///
138 | /// The mask used to generate a stripe's index.
139 | ///
140 | protected readonly int _stripeMask;
141 |
142 | ///
143 | /// Gets the striped instances in the
144 | /// .
145 | ///
146 | ///
147 | /// An containing the striped
148 | /// instances in the />
149 | ///
150 | public abstract IEnumerable Locks { get; }
151 |
152 | ///
153 | /// Gets the striped the key corresponds to.
154 | ///
155 | ///
156 | /// The key corresponding to the striped .
157 | ///
158 | ///
159 | /// The striped the key corresponds to.
160 | ///
161 | ///
162 | /// is null.
163 | ///
164 | public TLock this[TKey key]
165 | {
166 | get
167 | {
168 | if (key is null) throw new ArgumentNullException(nameof(key));
169 |
170 | return GetLock(key);
171 | }
172 | }
173 |
174 | ///
175 | /// Create a new instance with the specified
176 | /// amount of stripes, that creates new locking mechanism instances using the
177 | /// creatorFunction and uses the specified .
178 | ///
179 | ///
180 | /// The amount of stripes to divide the into.
181 | ///
182 | ///
183 | /// The function to create new instances.
184 | ///
185 | ///
186 | /// The implementation to use when generating
187 | /// a hash code in order to find a stripe.
188 | ///
189 | ///
190 | /// is less than 1.
191 | ///
192 | ///
193 | /// is null.
194 | ///
195 | protected Striped(
196 | int stripes,
197 | Func creatorFunction,
198 | IEqualityComparer? comparer)
199 | {
200 | if (stripes <= 0) throw new ArgumentOutOfRangeException(nameof(stripes));
201 |
202 | _creatorFunction = creatorFunction ?? throw new ArgumentNullException(nameof(creatorFunction));
203 | _comparer = comparer ?? EqualityComparer.Default;
204 | _stripeMask = GetStripeMask(stripes);
205 | }
206 |
207 | ///
208 | /// Gets the striped the key corresponds to.
209 | ///
210 | ///
211 | /// The key corresponding to the striped .
212 | ///
213 | ///
214 | /// The striped the key corresponds to.
215 | ///
216 | ///
217 | /// is null.
218 | ///
219 | public TLock GetLock(TKey key)
220 | {
221 | if (key is null) throw new ArgumentNullException(nameof(key));
222 |
223 | return GetLock(GetStripe(key));
224 | }
225 |
226 | ///
227 | /// Gets the striped s the keys correspond to in a sorted order.
228 | ///
229 | ///
230 | /// The keys corresponding to the striped s.
231 | ///
232 | ///
233 | /// The striped s the keys corresponds to in a sorted order.
234 | ///
235 | ///
236 | /// is null.
237 | ///
238 | public IEnumerable GetLocks(params TKey[] keys)
239 | {
240 | if (keys is null) throw new ArgumentNullException(nameof(keys));
241 |
242 | if (keys.Length == 0)
243 | {
244 | return Array.Empty();
245 | }
246 |
247 | var stripes = new int[keys.Length];
248 | for (var index = 0; index < keys.Length; index++)
249 | {
250 | stripes[index] = GetStripe(keys[index]);
251 | }
252 |
253 | Array.Sort(stripes);
254 |
255 | var locks = new TLock[stripes.Length];
256 | var lastStripe = stripes[0];
257 | locks[0] = GetLock(lastStripe);
258 | for (var index = 1; index < stripes.Length; index++)
259 | {
260 | var currentStripe = stripes[index];
261 | if (currentStripe == lastStripe)
262 | {
263 | locks[index] = locks[index - 1];
264 | }
265 | else
266 | {
267 | locks[index] = GetLock(currentStripe);
268 | lastStripe = currentStripe;
269 | }
270 | }
271 |
272 | return locks;
273 | }
274 |
275 | ///
276 | /// Gets the striped the key corresponds to.
277 | ///
278 | ///
279 | /// The index corresponding to the striped .
280 | ///
281 | ///
282 | /// The striped the key corresponds to.
283 | ///
284 | protected abstract TLock GetLock(int stripe);
285 |
286 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
287 | private int GetStripe(TKey key)
288 | {
289 | var hashCode = _comparer.GetHashCode(key) & int.MaxValue;
290 | return SmearHashCode(hashCode) & _stripeMask;
291 | }
292 |
293 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
294 | private int SmearHashCode(int hashCode)
295 | {
296 | hashCode ^= (hashCode >> 20) ^ (hashCode >> 12);
297 | return hashCode ^ (hashCode >> 7) ^ (hashCode >> 4);
298 | }
299 |
300 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
301 | private int GetStripeMask(int stripes)
302 | {
303 | stripes |= stripes >> 1;
304 | stripes |= stripes >> 2;
305 | stripes |= stripes >> 4;
306 | stripes |= stripes >> 8;
307 | stripes |= stripes >> 16;
308 | return stripes;
309 | }
310 | }
311 | }
--------------------------------------------------------------------------------