├── .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 | [![NuGet](https://img.shields.io/nuget/dt/AsyncUtilities.svg)](https://www.nuget.org/packages/AsyncUtilities) 3 | [![NuGet](https://img.shields.io/nuget/v/AsyncUtilities.svg)](https://www.nuget.org/packages/AsyncUtilities) 4 | [![license](https://img.shields.io/github/license/i3arnon/AsyncUtilities.svg)](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 | } --------------------------------------------------------------------------------