├── version.txt ├── pack.bat ├── .gitattributes ├── .gitignore ├── src ├── Directory.Build.props ├── ReusableTasks │ ├── NugetTargets │ │ └── ReusableTasks.targets │ ├── ReusableTasks │ │ ├── InvalidTaskReuseException.cs │ │ ├── SimpleSpinLock.cs │ │ ├── ReusableTask_T.cs │ │ ├── ReusableTaskCompletionSource.cs │ │ ├── ReusableTask.cs │ │ └── AsyncBoundedQueue.cs │ ├── ActionWorkItem.cs │ ├── System.Runtime.CompilerServices │ │ ├── IReusableTaskAwaiter.cs │ │ ├── EmptyStruct.cs │ │ ├── AsyncMethodBuilderAttribute.cs │ │ ├── ReusableTaskMethodBuilderCore.cs │ │ ├── StateMachineWithActionCache.cs │ │ ├── StateMachineCache.cs │ │ ├── ReusableTaskAwaiter.cs │ │ ├── ReusableTaskAwaiter_T.cs │ │ ├── AsyncVoidMethodBuilder.cs │ │ ├── ReusableTaskMethodBuilder.cs │ │ ├── ReusableTaskMethodBuilder_T.cs │ │ └── ResultHolder_T.cs │ └── ReusableTasks.csproj ├── Benchmark │ ├── Benchmark.csproj │ └── Program.cs ├── ReusableTasks.Tests │ ├── ReusableTasks.Tests.csproj │ ├── ReusableTaskExtensions.cs │ ├── TestSynchronizationContext.cs │ ├── ReusableTaskCompletionSource_TTests.cs │ ├── AsyncBoundedQueueTests.cs │ ├── ReusableTask_WithSyncContext_Tests.cs │ ├── ReusableTaskTests.cs │ └── ReusableTask_TTests.cs ├── ReusableTasks.sln └── .editorconfig ├── Makefile ├── CONTRIBUTING.md ├── LICENSE.md └── README.md /version.txt: -------------------------------------------------------------------------------- 1 | 4.0.0 2 | 3 | -------------------------------------------------------------------------------- /pack.bat: -------------------------------------------------------------------------------- 1 | msbuild /restore /t:Pack /p:Configuration=Release 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.cs text 2 | *.dll.config text 3 | *.exe.config text 4 | *.md text 5 | *.proj text 6 | *.props text 7 | *.resx text 8 | *.sh text eol=lf 9 | *.sln text eol=crlf 10 | *.targets text 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /out/ 3 | /src/.vs 4 | /src/Benchmark/bin 5 | /src/Benchmark/obj 6 | /src/ReusableTasks.Tests/bin/ 7 | /src/ReusableTasks.Tests/obj/ 8 | /src/ReusableTasks/bin/ 9 | /src/ReusableTasks/obj/ 10 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8.0 6 | true 7 | 8 | 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | XBUILD=msbuild 3 | XBUILD_ARGS=/nologo /restore 4 | 5 | all: 6 | @echo Building 7 | $(XBUILD) $(XBUILD_ARGS) 8 | 9 | clean: 10 | @echo Cleaning $(MAIN_SLN) 11 | $(XBUILD) $(XBUILD_ARGS) /t:Clean 12 | 13 | pack: 14 | @echo Creating the nupkg 15 | $(XBUILD) $(XBUILD_ARGS) /t:Pack /p:Configuration=Release 16 | 17 | .PHONY: all clean 18 | -------------------------------------------------------------------------------- /src/ReusableTasks/NugetTargets/ReusableTasks.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | AsyncVoidMethodBuilder.cs 7 | True 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Benchmark/Benchmark.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Compiling ReusableTask 2 | 3 | ### Pre-requisites 4 | * A version of the .NET framework, .NET Core or Mono which can compile .NET Standard 2.0 projects. 5 | * Any IDE which can compile C# code. 6 | 7 | ### MSBuild: 8 | To build using MSBuild, execute the following command: 9 | 10 | $ msbuild /restore 11 | 12 | ### Makefiles: 13 | To build using the Makefile, execute the following command: 14 | 15 | $ make 16 | 17 | ### Using an IDE: 18 | You can open src\ReusableTask.sln in any IDE. 19 | -------------------------------------------------------------------------------- /src/ReusableTasks.Tests/ReusableTasks.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | net8.0;net5.0;netcoreapp3.1;net472 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/ReusableTasks/ReusableTasks/InvalidTaskReuseException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReusableTasks 4 | { 5 | /// 6 | /// This exception is thrown whenever a has been mis-used. This can happen 7 | /// if the same instance is awaited twice. 8 | /// 9 | public class InvalidTaskReuseException : Exception 10 | { 11 | /// 12 | /// Creates a new instance of with the given message 13 | /// 14 | public InvalidTaskReuseException () 15 | { 16 | } 17 | 18 | /// 19 | /// Creates a new instance of with the given message 20 | /// 21 | /// The message describing the failure 22 | public InvalidTaskReuseException (string message) 23 | : base (message) 24 | { 25 | 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/ReusableTasks.Tests/ReusableTaskExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Threading.Tasks; 3 | 4 | using NUnit.Framework; 5 | 6 | namespace ReusableTasks.Tests 7 | { 8 | public static class ReusableTaskExtensions 9 | { 10 | static readonly int Timeout = Debugger.IsAttached ? -1 : 1000; 11 | 12 | public static async ReusableTask WithTimeout (this ReusableTask task, string message) 13 | { 14 | var t = task.AsTask (); 15 | if (await Task.WhenAny (Task.Delay (Timeout), t) != t) 16 | Assert.Fail ("The task timed out. {0}", message); 17 | await t; 18 | } 19 | 20 | public static async ReusableTask WithTimeout (this ReusableTask task, string message) 21 | { 22 | var t = task.AsTask (); 23 | if (await Task.WhenAny (Task.Delay (Timeout), t) != t) 24 | Assert.Fail ("The task timed out. {0}", message); 25 | return await t; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | // 2 | // ReusableTasks 3 | // 4 | // Authors: 5 | // Alan McGovern 6 | // 7 | // Copyright (C) 2019 Alan McGovern 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | -------------------------------------------------------------------------------- /src/ReusableTasks/ActionWorkItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Runtime.CompilerServices; 5 | using System.Threading; 6 | 7 | namespace ReusableTasks 8 | { 9 | #if !NETSTANDARD2_0 && !NETSTANDARD2_1 10 | class ActionWorkItem : IThreadPoolWorkItem 11 | { 12 | static readonly Action EmptyAction = () => { }; 13 | static readonly Stack Cache = new Stack (); 14 | static readonly SimpleSpinLock CacheLock = new SimpleSpinLock (); 15 | 16 | public object Continuation { get; private set; } = EmptyAction; 17 | 18 | public static ActionWorkItem GetOrCreate (object action) 19 | { 20 | using (CacheLock.Enter ()) { 21 | if (Cache.Count == 0) { 22 | return new ActionWorkItem { Continuation = action }; 23 | } else { 24 | var worker = Cache.Pop (); 25 | worker.Continuation = action; 26 | return worker; 27 | } 28 | } 29 | } 30 | 31 | public void Execute () 32 | { 33 | ResultHolder.Invoker (Continuation); 34 | Continuation = EmptyAction; 35 | 36 | using (CacheLock.Enter ()) 37 | Cache.Push (this); 38 | } 39 | } 40 | #endif 41 | } 42 | -------------------------------------------------------------------------------- /src/ReusableTasks/System.Runtime.CompilerServices/IReusableTaskAwaiter.cs: -------------------------------------------------------------------------------- 1 | // 2 | // ReusableTaskAwaiter_T.cs 3 | // 4 | // Authors: 5 | // Alan McGovern alan.mcgovern@gmail.com 6 | // 7 | // Copyright (C) 2019 Alan McGovern 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | namespace System.Runtime.CompilerServices 30 | { 31 | interface IReusableTaskAwaiter 32 | { 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ReusableTasks/System.Runtime.CompilerServices/EmptyStruct.cs: -------------------------------------------------------------------------------- 1 | // 2 | // EmptyStruct.cs 3 | // 4 | // Authors: 5 | // Alan McGovern alan.mcgovern@gmail.com 6 | // 7 | // Copyright (C) 2019 Alan McGovern 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | 30 | namespace System.Runtime.CompilerServices 31 | { 32 | /// 33 | /// Not intended to be used directly. 34 | /// 35 | readonly struct EmptyStruct 36 | { 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ReusableTask 2 | 3 | [![NuGet version](https://badge.fury.io/nu/reusabletasks.svg)](https://badge.fury.io/nu/reusabletasks) 4 | [![Build status](https://dev.azure.com/alanmcgovern0144/ReusableTasks/_apis/build/status/ReusableTasks-CI)](https://dev.azure.com/alanmcgovern0144/ReusableTasks/_build/latest?definitionId=3) 5 | 6 | A .NET Standard 2.0 compatible library which can be used to implement zero allocation async/await. This is conceptually similar to `ValueTask`, except it's compatible with .NET 2.0 and has zero ongoing allocations once the internal cache initializes. 7 | 8 | Sample usage: 9 | ``` 10 | public async ReusableTask InitializeAsync () 11 | { 12 | if (!Initialized) { 13 | await LongInitializationAsync (); 14 | Initialized = true; 15 | } 16 | } 17 | 18 | async ReusableTask LongInitializationAsync () 19 | { 20 | // Contact a database, load from a file, etc 21 | } 22 | 23 | public async ReusableTask CreateMyDataAsync () 24 | { 25 | if (!Initialized) { 26 | mydata = await LongMyDataCreationAsync (); 27 | Initialized = true; 28 | } 29 | return mydata; 30 | } 31 | 32 | async ReusableTask LongMyDataCreationAsync () 33 | { 34 | // Contact a database, load from a file, etc.. 35 | return new MyData (....); 36 | } 37 | ``` 38 | The compiler generated async state machine for these methods is allocation-free, and results in no garbage collectable objects being created no matter how many times the methods are invoked. 39 | 40 | The four things you cannot do with `ValueTask` you also cannot do with `ReusableTask`. The documentation can be read here in the remarks section, https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.valuetask-1?view=netcore-3.0#remarks. 41 | 42 | Unlike the documentation states for `ValueTask`, I would recommend that the default return value for any async method should be `ReusableTask` or `ReusableTask`, unless benchmarking shows otherwise. 43 | -------------------------------------------------------------------------------- /src/ReusableTasks/System.Runtime.CompilerServices/AsyncMethodBuilderAttribute.cs: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncMethodBuilderAttribute.cs 3 | // 4 | // Authors: 5 | // Alan McGovern alan.mcgovern@gmail.com 6 | // 7 | // Copyright (C) 2019 Alan McGovern 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | 30 | #if NETSTANDARD2_0 31 | 32 | namespace System.Runtime.CompilerServices 33 | { 34 | /// 35 | /// Not intended to be used directly. 36 | /// 37 | [AttributeUsage (AttributeTargets.Class | AttributeTargets.Struct)] 38 | public sealed class AsyncMethodBuilderAttribute : Attribute 39 | { 40 | /// 41 | /// 42 | /// 43 | public Type BuilderType { get; } 44 | 45 | /// 46 | /// 47 | /// 48 | /// 49 | public AsyncMethodBuilderAttribute (Type type) 50 | => BuilderType = type; 51 | } 52 | } 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /src/ReusableTasks/ReusableTasks/SimpleSpinLock.cs: -------------------------------------------------------------------------------- 1 | // 2 | // SimpleSpinLock.cs 3 | // 4 | // Authors: 5 | // Alan McGovern alan.mcgovern@gmail.com 6 | // 7 | // Copyright (C) 2024 Alan McGovern 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | using System; 30 | using System.Threading; 31 | 32 | namespace ReusableTasks 33 | { 34 | class SimpleSpinLock 35 | { 36 | SpinLock Lock = new SpinLock (false); 37 | 38 | public Releaser Enter () 39 | { 40 | // Nothing in this library is safe after a thread abort... so let's not care too much about this. 41 | bool taken = false; 42 | Lock.Enter (ref taken); 43 | return new Releaser (this); 44 | } 45 | 46 | public readonly struct Releaser : IDisposable 47 | { 48 | readonly SimpleSpinLock SimpleSpinLock; 49 | 50 | internal Releaser (SimpleSpinLock simpleSpinLock) 51 | => SimpleSpinLock = simpleSpinLock; 52 | 53 | public void Dispose () 54 | => SimpleSpinLock?.Lock.Exit (false); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/ReusableTasks.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 17 3 | VisualStudioVersion = 17.0.31825.309 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReusableTasks", "ReusableTasks\ReusableTasks.csproj", "{3BF57E6F-F7D8-49C9-B7B4-6FA3298A56D3}" 6 | EndProject 7 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReusableTasks.Tests", "ReusableTasks.Tests\ReusableTasks.Tests.csproj", "{99C257E6-09C2-4540-98B9-1A899D4D5948}" 8 | EndProject 9 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmark", "Benchmark\Benchmark.csproj", "{22CA3945-E4CC-4B86-B2FF-5014E79EAEB0}" 10 | EndProject 11 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{AE87BF75-7CAD-41CB-9E12-9BD4D4699404}" 12 | ProjectSection(SolutionItems) = preProject 13 | .editorconfig = .editorconfig 14 | EndProjectSection 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {3BF57E6F-F7D8-49C9-B7B4-6FA3298A56D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {3BF57E6F-F7D8-49C9-B7B4-6FA3298A56D3}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {3BF57E6F-F7D8-49C9-B7B4-6FA3298A56D3}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {3BF57E6F-F7D8-49C9-B7B4-6FA3298A56D3}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {99C257E6-09C2-4540-98B9-1A899D4D5948}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {99C257E6-09C2-4540-98B9-1A899D4D5948}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {99C257E6-09C2-4540-98B9-1A899D4D5948}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {99C257E6-09C2-4540-98B9-1A899D4D5948}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {22CA3945-E4CC-4B86-B2FF-5014E79EAEB0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {22CA3945-E4CC-4B86-B2FF-5014E79EAEB0}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {22CA3945-E4CC-4B86-B2FF-5014E79EAEB0}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {22CA3945-E4CC-4B86-B2FF-5014E79EAEB0}.Release|Any CPU.Build.0 = Release|Any CPU 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | GlobalSection(ExtensibilityGlobals) = postSolution 39 | SolutionGuid = {741D5826-AF23-4A6B-8A54-7D6215347C18} 40 | EndGlobalSection 41 | EndGlobal 42 | -------------------------------------------------------------------------------- /src/ReusableTasks/System.Runtime.CompilerServices/ReusableTaskMethodBuilderCore.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace System.Runtime.CompilerServices 6 | { 7 | /// 8 | /// Not intended to be used directly. 9 | /// 10 | public static class ReusableTaskMethodBuilderCore 11 | { 12 | /// 13 | /// 14 | /// 15 | /// 16 | /// 17 | /// 18 | /// 19 | public static void AwaitOnCompleted (ref TAwaiter awaiter, ref TStateMachine stateMachine) 20 | where TAwaiter : INotifyCompletion 21 | where TStateMachine : IAsyncStateMachine 22 | { 23 | if (awaiter is IReusableTaskAwaiter) { 24 | ref ReusableTaskAwaiter typedAwaiter = ref Unsafe.As (ref awaiter); 25 | var sm = StateMachineCache.GetOrCreate (); 26 | sm.SetStateMachine (ref stateMachine); 27 | typedAwaiter.ResultHolder.Continuation = sm; 28 | } else { 29 | var smwc = StateMachineWithActionCache.GetOrCreate (); 30 | smwc.SetStateMachine (ref stateMachine); 31 | awaiter.OnCompleted (smwc.Callback); 32 | } 33 | } 34 | 35 | /// 36 | /// 37 | /// 38 | /// 39 | /// 40 | /// 41 | /// 42 | public static void AwaitUnsafeOnCompleted (ref TAwaiter awaiter, ref TStateMachine stateMachine) 43 | where TAwaiter : ICriticalNotifyCompletion 44 | where TStateMachine : IAsyncStateMachine 45 | { 46 | if (awaiter is IReusableTaskAwaiter) { 47 | ref ReusableTaskAwaiter typedAwaiter = ref Unsafe.As (ref awaiter); 48 | var sm = StateMachineCache.GetOrCreate (); 49 | sm.SetStateMachine (ref stateMachine); 50 | typedAwaiter.ResultHolder.Continuation = sm; 51 | } else { 52 | var smwc = StateMachineWithActionCache.GetOrCreate (); 53 | smwc.SetStateMachine (ref stateMachine); 54 | awaiter.OnCompleted (smwc.Callback); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/ReusableTasks/System.Runtime.CompilerServices/StateMachineWithActionCache.cs: -------------------------------------------------------------------------------- 1 | // 2 | // StateMachineCache.cs 3 | // 4 | // Authors: 5 | // Alan McGovern alan.mcgovern@gmail.com 6 | // 7 | // Copyright (C) 2019 Alan McGovern 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | 30 | using System.Collections.Generic; 31 | using System.Threading; 32 | 33 | namespace System.Runtime.CompilerServices 34 | { 35 | /// 36 | /// Not intended to be used directly. 37 | /// 38 | public class StateMachineWithActionCache 39 | where TStateMachine : IAsyncStateMachine 40 | { 41 | #pragma warning disable RECS0108 // Warns about static fields in generic types 42 | static readonly Stack> Cache = new Stack> (); 43 | static readonly ReusableTasks.SimpleSpinLock CacheLock = new ReusableTasks.SimpleSpinLock (); 44 | #pragma warning restore RECS0108 // Warns about static fields in generic types 45 | 46 | /// 47 | /// Retrieves an instance of from the cache. If the cache is 48 | /// instance is added back into the cache as soon as the 49 | /// async continuation has been executed. 50 | /// 51 | /// 52 | public static StateMachineWithActionCache GetOrCreate () 53 | { 54 | using (CacheLock.Enter ()) 55 | return Cache.Count > 0 ? Cache.Pop () : new StateMachineWithActionCache (); 56 | } 57 | 58 | internal readonly Action Callback; 59 | TStateMachine StateMachine; 60 | 61 | StateMachineWithActionCache () 62 | { 63 | Callback = OnCompleted; 64 | } 65 | 66 | /// 67 | /// 68 | /// 69 | /// 70 | public void SetStateMachine (ref TStateMachine stateMachine) 71 | { 72 | StateMachine = stateMachine; 73 | } 74 | 75 | void OnCompleted () 76 | { 77 | // Run the callback *before* pushing this object back into the cache. 78 | // This makes things a teeny bit more responsive. 79 | StateMachine.MoveNext (); 80 | StateMachine = default; 81 | 82 | using (CacheLock.Enter ()) 83 | Cache.Push (this); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/ReusableTasks.Tests/TestSynchronizationContext.cs: -------------------------------------------------------------------------------- 1 | // 2 | // TestSynchronizationContext.cs 3 | // 4 | // Authors: 5 | // Alan McGovern alan.mcgovern@gmail.com 6 | // 7 | // Copyright (C) 2019 Alan McGovern 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | 30 | using System; 31 | using System.Collections.Concurrent; 32 | using System.Runtime.CompilerServices; 33 | using System.Threading; 34 | 35 | namespace ReusableTasks.Tests 36 | { 37 | public class TestSynchronizationContext : SynchronizationContext, INotifyCompletion 38 | { 39 | public static readonly TestSynchronizationContext Instance = new TestSynchronizationContext (); 40 | 41 | public int Posted; 42 | public int Sent; 43 | BlockingCollection Callbacks = new BlockingCollection (); 44 | 45 | readonly Thread thread; 46 | 47 | TestSynchronizationContext () 48 | { 49 | thread = new Thread (state => { 50 | SetSynchronizationContext (this); 51 | while (true) { 52 | try { 53 | var task = Callbacks.Take (); 54 | task.Invoke (); 55 | } catch { 56 | break; 57 | } 58 | } 59 | }) { 60 | IsBackground = true, 61 | Name = "TestSynchronizationContext Thread", 62 | }; 63 | thread.Start (); 64 | } 65 | 66 | public void ResetCounts () 67 | { 68 | Posted = 0; 69 | Sent = 0; 70 | } 71 | 72 | public override void Post (SendOrPostCallback d, object state) 73 | { 74 | Posted++; 75 | 76 | Callbacks.Add (() => d (state)); 77 | } 78 | 79 | public override void Send (SendOrPostCallback d, object state) 80 | { 81 | Sent++; 82 | 83 | var waiter = new ManualResetEventSlim (false); 84 | Action action = () => { 85 | d (state); 86 | waiter.Set (); 87 | }; 88 | Callbacks.Add (action); 89 | waiter.Wait (); 90 | waiter.Dispose (); 91 | } 92 | 93 | public TestSynchronizationContext GetAwaiter () 94 | => this; 95 | 96 | public bool IsCompleted => thread == Thread.CurrentThread; 97 | 98 | public void GetResult () 99 | { 100 | 101 | } 102 | 103 | void INotifyCompletion.OnCompleted (Action continuation) 104 | => Callbacks.Add (continuation); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/ReusableTasks/System.Runtime.CompilerServices/StateMachineCache.cs: -------------------------------------------------------------------------------- 1 | // 2 | // StateMachineCache.cs 3 | // 4 | // Authors: 5 | // Alan McGovern alan.mcgovern@gmail.com 6 | // 7 | // Copyright (C) 2019 Alan McGovern 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | 30 | using System.Collections.Generic; 31 | 32 | namespace System.Runtime.CompilerServices 33 | { 34 | /// 35 | /// Not intended to be used directly. 36 | /// 37 | abstract class StateMachineCache 38 | { 39 | internal virtual void OnCompleted () 40 | { 41 | } 42 | } 43 | 44 | /// 45 | /// Not intended to be used directly. 46 | /// 47 | sealed class StateMachineCache : StateMachineCache 48 | where TStateMachine : IAsyncStateMachine 49 | { 50 | #pragma warning disable RECS0108 // Warns about static fields in generic types 51 | static readonly Stack> Cache = new Stack> (); 52 | static readonly ReusableTasks.SimpleSpinLock CacheLock = new ReusableTasks.SimpleSpinLock (); 53 | #pragma warning restore RECS0108 // Warns about static fields in generic types 54 | 55 | /// 56 | /// Retrieves an instance of from the cache. If the cache is 57 | /// empty, a new instance will be created and returned. You must invoke either or 58 | /// . This will ensure the 59 | /// instance is added back into the cache as soon as the 60 | /// async continuation has been executed. 61 | /// 62 | /// 63 | public static StateMachineCache GetOrCreate () 64 | { 65 | using (CacheLock.Enter ()) 66 | return Cache.Count > 0 ? Cache.Pop () : new StateMachineCache (); 67 | } 68 | 69 | TStateMachine StateMachine; 70 | 71 | /// 72 | /// 73 | /// 74 | /// 75 | public void SetStateMachine (ref TStateMachine stateMachine) 76 | { 77 | StateMachine = stateMachine; 78 | } 79 | 80 | internal override void OnCompleted () 81 | { 82 | // Run the callback *before* pushing this object back into the cache. 83 | // This makes things a teeny bit more responsive. 84 | StateMachine.MoveNext (); 85 | StateMachine = default; 86 | 87 | using (CacheLock.Enter ()) 88 | Cache.Push (this); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/ReusableTasks/System.Runtime.CompilerServices/ReusableTaskAwaiter.cs: -------------------------------------------------------------------------------- 1 | // 2 | // ReusableTaskAwaiter.cs 3 | // 4 | // Authors: 5 | // Alan McGovern alan.mcgovern@gmail.com 6 | // 7 | // Copyright (C) 2019 Alan McGovern 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | 30 | using System.Runtime.ExceptionServices; 31 | 32 | using ReusableTasks; 33 | 34 | namespace System.Runtime.CompilerServices 35 | { 36 | /// 37 | /// Not intended to be used directly. 38 | /// 39 | public readonly struct ReusableTaskAwaiter : INotifyCompletion, IReusableTaskAwaiter 40 | { 41 | internal readonly ResultHolder ResultHolder; 42 | readonly int Id; 43 | 44 | /// 45 | /// 46 | /// 47 | public bool IsCompleted => ResultHolder == null || (ResultHolder.HasValue && !ResultHolder.ForceAsynchronousContinuation); 48 | 49 | 50 | /// 51 | /// 52 | /// 53 | /// 54 | /// 55 | internal ReusableTaskAwaiter (int id, ResultHolder resultHolder) 56 | { 57 | Id = id; 58 | ResultHolder = resultHolder; 59 | } 60 | 61 | /// 62 | /// 63 | /// 64 | public void GetResult () 65 | { 66 | // This respresents a 'ReusableTask.CompletedTask'. This is a successfully completed task with no errors. 67 | if (ResultHolder == null) 68 | return; 69 | 70 | if (ResultHolder.Id != Id) 71 | throw new InvalidTaskReuseException ("A mismatch was detected between the ResuableTask and its Result source. This typically means the ReusableTask was awaited twice concurrently. If you need to do this, convert the ReusableTask to a Task before awaiting."); 72 | 73 | var exception = ResultHolder.Exception; 74 | if (ResultHolder.Cacheable) 75 | ReusableTaskMethodBuilder.Release (ResultHolder); 76 | 77 | if (exception != null) 78 | ExceptionDispatchInfo.Capture (exception).Throw (); 79 | } 80 | 81 | /// 82 | /// 83 | /// 84 | /// 85 | public void OnCompleted (Action continuation) 86 | { 87 | if (ResultHolder.Id != Id) 88 | throw new InvalidTaskReuseException ("A mismatch was detected between the ResuableTask and its Result source. This typically means the ReusableTask was awaited twice concurrently. If you need to do this, convert the ReusableTask to a Task before awaiting."); 89 | 90 | ResultHolder.Continuation = continuation; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Benchmark/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.Runtime.CompilerServices; 5 | using System.Threading.Tasks; 6 | 7 | using BenchmarkDotNet.Attributes; 8 | using BenchmarkDotNet.Configs; 9 | using BenchmarkDotNet.Running; 10 | 11 | using ReusableTasks; 12 | 13 | namespace MyBenchmarks 14 | { 15 | [MemoryDiagnoser] 16 | public class ReusableTask_AlreadyCompleted 17 | { 18 | [Params (1, 4)] 19 | public int Concurrency { get; set; } = 1; 20 | 21 | [Params (500)] 22 | public int Depth { get; set; } = 500; 23 | 24 | [Params (1000)] 25 | public int Iterations { get; set; } = 1000; 26 | 27 | public ReusableTask_AlreadyCompleted () 28 | { 29 | ReusableTaskMethodBuilder.MaximumCacheSize = Depth * 4; 30 | } 31 | 32 | [Benchmark] 33 | public async Task ReusableTask () 34 | { 35 | static async ReusableTask AsyncCompletion (int count) 36 | { 37 | if (count == 0) { 38 | await Task.Yield (); 39 | } else { 40 | await AsyncCompletion (count - 1).ConfigureAwait (false); 41 | } 42 | } 43 | 44 | for (int i = 0; i < Iterations; i++) 45 | await AsyncCompletion (Depth).ConfigureAwait (false); 46 | } 47 | 48 | [Benchmark] 49 | public void ReusableTaskInt () 50 | { 51 | async Task Async_SyncCompletion_Looper () 52 | { 53 | async ReusableTask AsyncCompletion (int count) 54 | { 55 | if (count == 0) { 56 | await Task.Yield (); 57 | return 10; 58 | } else { 59 | return await AsyncCompletion (count - 1).ConfigureAwait (false); 60 | } 61 | } 62 | 63 | for (int i = 0; i < Iterations; i++) 64 | await AsyncCompletion (Depth).ConfigureAwait (false); 65 | } 66 | 67 | Task.WhenAll (Enumerable.Range (0, Concurrency) 68 | .Select (t => Task.Run (Async_SyncCompletion_Looper))) 69 | .Wait (); 70 | } 71 | 72 | [Benchmark] 73 | public void ValueTaskInt () 74 | { 75 | async Task Async_SyncCompletion_Looper () 76 | { 77 | async ValueTask AsyncCompletion (int count) 78 | { 79 | if (count == 0) { 80 | await Task.Yield (); 81 | return 10; 82 | } else { 83 | return await AsyncCompletion (count - 1).ConfigureAwait (false); 84 | } 85 | } 86 | 87 | for (int i = 0; i < Iterations; i++) 88 | await AsyncCompletion (Depth).ConfigureAwait (false); 89 | } 90 | 91 | Task.WhenAll (Enumerable.Range (0, Concurrency) 92 | .Select (t => Task.Run (Async_SyncCompletion_Looper))) 93 | .Wait (); 94 | } 95 | } 96 | 97 | public class Program 98 | { 99 | public static void Main (string[] args) 100 | { 101 | //RunTest ().Wait (); 102 | //var summary = BenchmarkRunner.Run (typeof (ReusableTask_AlreadyCompleted).Assembly, new DebugInProcessConfig()); 103 | var summary = BenchmarkRunner.Run (typeof (ReusableTask_AlreadyCompleted).Assembly); 104 | } 105 | 106 | static async Task RunTest () 107 | { 108 | ReusableTask_AlreadyCompleted a = new ReusableTask_AlreadyCompleted (); 109 | for (int i = 0; i < 10000; i++) { 110 | a.ReusableTask (); 111 | await Task.Yield (); 112 | } 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/ReusableTasks/System.Runtime.CompilerServices/ReusableTaskAwaiter_T.cs: -------------------------------------------------------------------------------- 1 | // 2 | // ReusableTaskAwaiter_T.cs 3 | // 4 | // Authors: 5 | // Alan McGovern alan.mcgovern@gmail.com 6 | // 7 | // Copyright (C) 2019 Alan McGovern 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | 30 | using System.Runtime.ExceptionServices; 31 | 32 | using ReusableTasks; 33 | 34 | namespace System.Runtime.CompilerServices 35 | { 36 | /// 37 | /// Not intended to be used directly. 38 | /// 39 | public readonly struct ReusableTaskAwaiter : INotifyCompletion, IReusableTaskAwaiter 40 | { 41 | internal readonly ResultHolder ResultHolder; 42 | readonly int Id; 43 | readonly T Result; 44 | 45 | /// 46 | /// 47 | /// 48 | public bool IsCompleted => ResultHolder == null || (ResultHolder.HasValue && !ResultHolder.ForceAsynchronousContinuation); 49 | 50 | /// 51 | /// 52 | /// 53 | /// 54 | /// 55 | /// 56 | internal ReusableTaskAwaiter (int id, ResultHolder resultHolder, in T result) 57 | { 58 | Id = id; 59 | ResultHolder = resultHolder; 60 | Result = result; 61 | } 62 | 63 | /// 64 | /// 65 | /// 66 | /// 67 | public T GetResult () 68 | { 69 | // Represents a successfully completed task no data. 70 | if (ResultHolder == null) 71 | return Result; 72 | 73 | if (ResultHolder.Id != Id) 74 | throw new InvalidTaskReuseException ("A mismatch was detected between the ResuableTask and its Result source. This typically means the ReusableTask was awaited twice concurrently. If you need to do this, convert the ReusableTask to a Task before awaiting."); 75 | 76 | var result = ResultHolder.GetResult (); 77 | var exception = ResultHolder.Exception; 78 | ReusableTaskMethodBuilder.Release (ResultHolder); 79 | 80 | if (exception != null) 81 | ExceptionDispatchInfo.Capture (exception).Throw (); 82 | return result; 83 | } 84 | 85 | /// 86 | /// 87 | /// 88 | /// 89 | public void OnCompleted (Action continuation) 90 | { 91 | if (ResultHolder.Id != Id) 92 | throw new InvalidTaskReuseException ("A mismatch was detected between the ResuableTask and its Result source. This typically means the ReusableTask was awaited twice concurrently. If you need to do this, convert the ReusableTask to a Task before awaiting."); 93 | 94 | ResultHolder.Continuation = continuation; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/ReusableTasks/System.Runtime.CompilerServices/AsyncVoidMethodBuilder.cs: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncVoidMethodBuilder.cs 3 | // 4 | // Authors: 5 | // Alan McGovern alan.mcgovern@gmail.com 6 | // 7 | // Copyright (C) 2019 Alan McGovern 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | 30 | #pragma warning disable CS0436 // Type conflicts with imported type 31 | 32 | namespace System.Runtime.CompilerServices 33 | { 34 | /// 35 | /// A reimplementation of the built-in AsyncVoidMethodBuilder which is backed by 36 | /// instead of . 37 | /// 38 | readonly struct AsyncVoidMethodBuilder 39 | { 40 | /// 41 | /// 42 | /// 43 | /// 44 | public static AsyncVoidMethodBuilder Create () => new AsyncVoidMethodBuilder (); 45 | 46 | /// 47 | /// 48 | /// 49 | /// 50 | public void SetException (Exception e) 51 | { 52 | } 53 | 54 | /// 55 | /// 56 | /// 57 | public void SetResult () 58 | { 59 | } 60 | 61 | /// 62 | /// 63 | /// 64 | /// 65 | public void SetStateMachine (IAsyncStateMachine stateMachine) 66 | { 67 | } 68 | 69 | /// 70 | /// 71 | /// 72 | /// 73 | /// 74 | public void Start (ref TStateMachine stateMachine) 75 | where TStateMachine : IAsyncStateMachine 76 | { 77 | stateMachine.MoveNext (); 78 | } 79 | 80 | /// 81 | /// 82 | /// 83 | /// 84 | /// 85 | /// 86 | /// 87 | public void AwaitOnCompleted (ref TAwaiter awaiter, ref TStateMachine stateMachine) 88 | where TAwaiter : INotifyCompletion 89 | where TStateMachine : IAsyncStateMachine 90 | { 91 | ReusableTaskMethodBuilderCore.AwaitOnCompleted (ref awaiter, ref stateMachine); 92 | } 93 | 94 | /// 95 | /// 96 | /// 97 | /// 98 | /// 99 | /// 100 | /// 101 | public void AwaitUnsafeOnCompleted (ref TAwaiter awaiter, ref TStateMachine stateMachine) 102 | where TAwaiter : ICriticalNotifyCompletion 103 | where TStateMachine : IAsyncStateMachine 104 | { 105 | ReusableTaskMethodBuilderCore.AwaitUnsafeOnCompleted (ref awaiter, ref stateMachine); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/ReusableTasks/ReusableTasks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | netstandard2.0;netstandard2.1;net5.0;netcoreapp3.0 6 | portable 7 | 8 | true 9 | 10 | version.txt 11 | true 12 | true 13 | $([MSBuild]::GetDirectoryNameOfFileAbove ('$(MSBuildThisFileDirectory)', 'version.txt'))\ 14 | 15 | -alpha 16 | -beta 17 | 18 | $(BeforePack);SetPackProperties 19 | $(PackDependsOn) 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 0.0.0.1 29 | $(Version) 30 | $(Version) 31 | $(Version)-Debug 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | $(GitSemVerMajor).$(GitSemVerMinor).$(GitSemVerPatch) 40 | $(ReusableTasksFileVersion)-$(GitBranch)+$(GitCommit) 41 | 42 | $(ReusableTasksABIVersion) 43 | $(ReusableTasksFileVersion) 44 | $(ReusableTasksInformationalVersion) 45 | 46 | 47 | 48 | 50 | 51 | 52 | Alan McGovern 53 | ReusableTasks is a (nearly) zero allocation Task-like object for use when declaring async methods. 54 | LICENSE.md 55 | https://github.com/alanmcgovern/ReusableTasks 56 | false 57 | $(ReusableTasksFileVersion)$(ReusableTasksReleaseSuffix) 58 | threading;async;task;tasks 59 | 60 | true 61 | true 62 | snupkg 63 | 64 | git 65 | $(GitBranch) 66 | $(GitSha) 67 | https://github.com/alanmcgovern/ReusableTasks 68 | 69 | Alan McGovern 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/ReusableTasks/ReusableTasks/ReusableTask_T.cs: -------------------------------------------------------------------------------- 1 | // 2 | // ReusableTask_T.cs 3 | // 4 | // Authors: 5 | // Alan McGovern alan.mcgovern@gmail.com 6 | // 7 | // Copyright (C) 2019 Alan McGovern 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | 30 | using System.Runtime.CompilerServices; 31 | using System.Threading; 32 | using System.Threading.Tasks; 33 | 34 | namespace ReusableTasks 35 | { 36 | /// 37 | /// This is equivalent to a , except the underlying 38 | /// instance is cached and re-used. If an async method using is declared, 39 | /// the returned must be awaited exactly once. If the 40 | /// is not awaited then it will not be returned to the cache for reuse. There are no other negative effects. 41 | /// If an instance of is awaited twice, then it will corrupt the cache and 42 | /// future behaviour will be indeterminate. 43 | /// 44 | [AsyncMethodBuilder (typeof (ReusableTaskMethodBuilder<>))] 45 | public readonly struct ReusableTask 46 | { 47 | // The order of these fields needs to match the declaration order in `ReusableTask`. 48 | // Both structs are cast using `Unsafe.As` so the fields need to match so 'ResultHolder' 49 | // can be safely accessed 50 | internal readonly ResultHolder ResultHolder; 51 | readonly int Id; 52 | readonly T Result; 53 | 54 | 55 | /// 56 | /// Returns true if the task has completed. 57 | /// 58 | public bool IsCompleted => ResultHolder == null || (ResultHolder.HasValue && !ResultHolder.ForceAsynchronousContinuation); 59 | 60 | internal ReusableTask (ResultHolder resultHolder) 61 | { 62 | Result = default; 63 | ResultHolder = resultHolder; 64 | Id = ResultHolder.Id; 65 | } 66 | 67 | internal ReusableTask (ResultHolder resultHolder, SynchronizationContext syncContext) 68 | : this (resultHolder) 69 | { 70 | ResultHolder.SyncContext = syncContext; 71 | } 72 | 73 | internal ReusableTask (T result) 74 | { 75 | Result = result; 76 | ResultHolder = default; 77 | Id = default; 78 | } 79 | 80 | /// 81 | /// Converts this into a standard 82 | /// 83 | /// 84 | /// 85 | public async Task AsTask () 86 | => await this; 87 | 88 | /// 89 | /// Configures the awaiter used by this 90 | /// 91 | /// If then the continuation will 92 | /// be invoked on the captured , otherwise 93 | /// the continuation will be executed on a thread. 94 | /// 95 | public ReusableTask ConfigureAwait (bool continueOnCapturedContext) 96 | { 97 | if (ResultHolder != null) { 98 | if (continueOnCapturedContext) 99 | ResultHolder.SyncContext = SynchronizationContext.Current; 100 | else 101 | ResultHolder.SyncContext = null; 102 | } 103 | return this; 104 | } 105 | 106 | /// 107 | /// Gets the awaiter used to await this 108 | /// 109 | /// 110 | public ReusableTaskAwaiter GetAwaiter () 111 | => new ReusableTaskAwaiter (Id, ResultHolder, in Result); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/ReusableTasks/ReusableTasks/ReusableTaskCompletionSource.cs: -------------------------------------------------------------------------------- 1 | // 2 | // ReusableTaskCompletionSource.cs 3 | // 4 | // Authors: 5 | // Alan McGovern alan.mcgovern@gmail.com 6 | // 7 | // Copyright (C) 2019 Alan McGovern 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | 30 | using System; 31 | using System.Runtime.CompilerServices; 32 | using System.Threading; 33 | using System.Threading.Tasks; 34 | 35 | namespace ReusableTasks 36 | { 37 | /// 38 | /// This is equivalent to a where the underlying 39 | /// instance is reset after it has been awaited and completed. 40 | /// 41 | /// 42 | public class ReusableTaskCompletionSource 43 | { 44 | ResultHolder Result { get; } 45 | 46 | /// 47 | /// The controlled by this . 48 | /// Once the Task has been both completed and awaited it will be reset to it's initial state, allowing 49 | /// this instance to be reused. 50 | /// 51 | public ReusableTask Task => new ReusableTask (Result, SynchronizationContext.Current); 52 | 53 | /// 54 | /// Instantiates a new . 55 | /// 56 | public ReusableTaskCompletionSource () 57 | : this (false) 58 | { 59 | } 60 | 61 | /// 62 | /// The controlled by this . 63 | /// Once the Task has been both completed and awaited it will be reset to it's initial state, allowing 64 | /// this instance to be reused. 65 | /// 66 | /// True if the continuation should always be invoked asynchronously. 67 | public ReusableTaskCompletionSource (bool forceAsynchronousContinuation) 68 | { 69 | Result = new ResultHolder (false, forceAsynchronousContinuation); 70 | } 71 | 72 | /// 73 | /// Moves to the Canceled state. 74 | /// 75 | public void SetCanceled () 76 | => Result.SetCanceled (); 77 | 78 | /// 79 | /// Moves to the Faulted state using the specified exception. 80 | /// 81 | public void SetException (Exception exception) 82 | => Result.SetException (exception); 83 | 84 | /// 85 | /// Moves to the Faulted state using the specified exception. 86 | /// 87 | public void SetResult (T result) 88 | => Result.SetResult (result); 89 | 90 | /// 91 | /// Returns true if the underlying task is successfully marked as canceled. Returns false 92 | /// if the underlying task has already completed. 93 | /// 94 | /// 95 | internal bool TrySetCanceled () 96 | => Result.TrySetCanceled (); 97 | 98 | /// 99 | /// Returns true if the underlying task is successfully marked as faulted. Returns false 100 | /// if the underlying task has already completed. 101 | /// 102 | /// 103 | internal bool TrySetException (Exception exception) 104 | => Result.TrySetException (exception); 105 | 106 | /// 107 | /// Returns true if the underlying task is successfully marked as completed. Returns false 108 | /// if the underlying task has already completed. 109 | /// 110 | /// 111 | internal bool TrySetResult (T result) 112 | => Result.TrySetResult (result); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/ReusableTasks/ReusableTasks/ReusableTask.cs: -------------------------------------------------------------------------------- 1 | // 2 | // ReusableTask.cs 3 | // 4 | // Authors: 5 | // Alan McGovern alan.mcgovern@gmail.com 6 | // 7 | // Copyright (C) 2019 Alan McGovern 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | 30 | using System.Runtime.CompilerServices; 31 | using System.Threading; 32 | using System.Threading.Tasks; 33 | 34 | namespace ReusableTasks 35 | { 36 | /// 37 | /// This is equivalent to a , except the underlying 38 | /// instance is cached and re-used. If an async method using is declared, 39 | /// the returned must be awaited exactly once. If the 40 | /// is not awaited then it will not be returned to the cache for reuse. There are no other negative effects. 41 | /// If an instance of is awaited twice, then it will corrupt the cache and 42 | /// future behaviour will be indeterminate. 43 | /// 44 | [AsyncMethodBuilder (typeof (ReusableTaskMethodBuilder))] 45 | public readonly struct ReusableTask 46 | { 47 | /// 48 | /// Gets an instance of which has already been completed. It is safe 49 | /// to await this instance multiple times. 50 | /// 51 | public static ReusableTask CompletedTask => default; 52 | 53 | /// 54 | /// Returns a completed task representing the result 55 | /// 56 | /// 57 | /// 58 | /// 59 | public static ReusableTask FromResult (T result) 60 | => new ReusableTask (result); 61 | 62 | // The order of these fields needs to match the declaration order in `ReusableTask`. 63 | // Both structs are cast using `Unsafe.As` so the fields need to match so 'ResultHolder' 64 | // can be safely accessed 65 | internal readonly ResultHolder ResultHolder; 66 | readonly int Id; 67 | 68 | /// 69 | /// Returns true if the task has completed. 70 | /// 71 | public bool IsCompleted => ResultHolder == null || (ResultHolder.HasValue && !ResultHolder.ForceAsynchronousContinuation); 72 | 73 | internal ReusableTask (ResultHolder resultHolder) 74 | { 75 | ResultHolder = resultHolder; 76 | Id = ResultHolder.Id; 77 | } 78 | 79 | /// 80 | /// Converts this into a standard 81 | /// 82 | /// 83 | /// 84 | public async Task AsTask () 85 | => await this; 86 | 87 | /// 88 | /// Configures the awaiter used by this 89 | /// 90 | /// If then the continuation will 91 | /// be invoked on the captured , otherwise 92 | /// the continuation will be executed on a thread. 93 | /// 94 | public ReusableTask ConfigureAwait (bool continueOnCapturedContext) 95 | { 96 | if (ResultHolder != null) { 97 | if (continueOnCapturedContext) 98 | ResultHolder.SyncContext = SynchronizationContext.Current; 99 | else 100 | ResultHolder.SyncContext = null; 101 | } 102 | return this; 103 | } 104 | 105 | /// 106 | /// Gets the awaiter used to await this 107 | /// 108 | /// 109 | public ReusableTaskAwaiter GetAwaiter () 110 | => new ReusableTaskAwaiter (Id, ResultHolder); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/ReusableTasks.Tests/ReusableTaskCompletionSource_TTests.cs: -------------------------------------------------------------------------------- 1 | // 2 | // ReusableTaskCompletionSource_TTests.cs 3 | // 4 | // Authors: 5 | // Alan McGovern alan.mcgovern@gmail.com 6 | // 7 | // Copyright (C) 2019 Alan McGovern 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | using System; 30 | using System.Collections.Generic; 31 | using System.Runtime.CompilerServices; 32 | using System.Threading.Tasks; 33 | 34 | using NUnit.Framework; 35 | 36 | namespace ReusableTasks.Tests 37 | { 38 | [TestFixture] 39 | public class ReusableTaskCompletionSource_TTests 40 | { 41 | [SetUp] 42 | public void Setup () 43 | { 44 | ReusableTaskMethodBuilder.ClearCache (); 45 | } 46 | 47 | [Test] 48 | public async Task DoNotForceAsyncWithSyncContext () 49 | { 50 | await TestSynchronizationContext.Instance; 51 | TestSynchronizationContext.Instance.ResetCounts (); 52 | 53 | var tcs = new ReusableTaskCompletionSource (false); 54 | tcs.SetResult (1); 55 | 56 | // If we are not forcing async, we do allow synchronous completion. 57 | Assert.IsTrue (tcs.Task.IsCompleted, "#1"); 58 | await tcs.Task; 59 | 60 | Assert.AreEqual (0, TestSynchronizationContext.Instance.Posted, "#2"); 61 | } 62 | 63 | [Test] 64 | public async Task ForceAsyncWithSyncContext () 65 | { 66 | await TestSynchronizationContext.Instance; 67 | TestSynchronizationContext.Instance.ResetCounts (); 68 | 69 | var tcs = new ReusableTaskCompletionSource (true); 70 | tcs.SetResult (1); 71 | 72 | // If we're forcing async, we have to explicitly disallow synchronous completion too. 73 | Assert.IsFalse (tcs.Task.IsCompleted, "#1"); 74 | await tcs.Task; 75 | 76 | Assert.AreEqual (1, TestSynchronizationContext.Instance.Posted, "#2"); 77 | } 78 | 79 | [Test] 80 | public async Task NotInCache () 81 | { 82 | _ = new ReusableTaskCompletionSource (); 83 | Assert.AreEqual (0, ReusableTaskMethodBuilder.CacheCount, "#1"); 84 | 85 | var tcs = new ReusableTaskCompletionSource (); 86 | tcs.SetResult (5); 87 | await tcs.Task; 88 | Assert.AreEqual (0, ReusableTaskMethodBuilder.CacheCount, "#2"); 89 | 90 | tcs = new ReusableTaskCompletionSource (); 91 | tcs.SetResult (5); 92 | await tcs.Task; 93 | Assert.AreEqual (0, ReusableTaskMethodBuilder.CacheCount, "#2"); 94 | } 95 | 96 | [Test] 97 | public async Task UseTwice () 98 | { 99 | var tcs = new ReusableTaskCompletionSource (); 100 | 101 | tcs.SetResult (1); 102 | Assert.IsTrue (tcs.Task.IsCompleted, "#1"); 103 | Assert.AreEqual (1, await tcs.Task, "#2"); 104 | Assert.IsFalse (tcs.Task.IsCompleted, "#3"); 105 | Assert.AreEqual (0, ReusableTaskMethodBuilder.CacheCount, "#4"); 106 | 107 | tcs.SetResult (2); 108 | Assert.AreEqual (2, await tcs.Task, "#6"); 109 | Assert.AreEqual (0, ReusableTaskMethodBuilder.CacheCount, "#7"); 110 | } 111 | 112 | [Test] 113 | public async Task StressTest_NoReuse () 114 | { 115 | var tasks = new List (); 116 | 117 | for (int count = 0; count < Environment.ProcessorCount * 2; count++) { 118 | tasks.Add (Task.Run (async () => { 119 | for (int i = 0; i < 50000; i++) { 120 | var tcs = new ReusableTaskCompletionSource (); 121 | await Task.WhenAll ( 122 | Task.Run (() => { tcs.SetResult (111); }), 123 | Task.Run (async () => { 124 | var result = await tcs.Task; 125 | Assert.AreEqual (111, result); 126 | }) 127 | ); 128 | } 129 | })); 130 | } 131 | 132 | await Task.WhenAll (tasks); 133 | } 134 | 135 | [Test] 136 | public async Task StressTest_Reuse () 137 | { 138 | var tasks = new List (); 139 | 140 | for (int count = 0; count < Environment.ProcessorCount * 2; count++) { 141 | tasks.Add (Task.Run (async () => { 142 | var tcs = new ReusableTaskCompletionSource (); 143 | for (int i = 0; i < 50000; i++) { 144 | await Task.WhenAll ( 145 | Task.Run (() => { tcs.SetResult (111); }), 146 | Task.Run (async () => { 147 | var result = await tcs.Task; 148 | Assert.AreEqual (111, result); 149 | }) 150 | ); 151 | } 152 | })); 153 | } 154 | 155 | await Task.WhenAll (tasks); 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/ReusableTasks/System.Runtime.CompilerServices/ReusableTaskMethodBuilder.cs: -------------------------------------------------------------------------------- 1 | // 2 | // ReusableTaskMethodBuilder.cs 3 | // 4 | // Authors: 5 | // Alan McGovern alan.mcgovern@gmail.com 6 | // 7 | // Copyright (C) 2019 Alan McGovern 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | 30 | using System.Collections.Generic; 31 | using System.Threading; 32 | 33 | using ReusableTasks; 34 | 35 | namespace System.Runtime.CompilerServices 36 | { 37 | /// 38 | /// Not intended to be used directly. 39 | /// 40 | public struct ReusableTaskMethodBuilder 41 | { 42 | /// 43 | /// The number of instances currently in the cache. 44 | /// 45 | public static int CacheCount => ReusableTaskMethodBuilder.CacheCount; 46 | 47 | /// 48 | /// Removes all instances from the cache. 49 | /// 50 | public static void ClearCache () 51 | => ReusableTaskMethodBuilder.ClearCache (); 52 | 53 | /// 54 | /// The maximum number of instances to store in the cache. Defaults to 55 | /// 56 | public static int MaximumCacheSize { get; set; } = 512; 57 | 58 | /// 59 | /// Not intended to be used directly. 60 | /// 61 | /// 62 | public static ReusableTaskMethodBuilder Create () 63 | => new ReusableTaskMethodBuilder (); 64 | 65 | /// 66 | /// Places the instance into the cache for re-use. This is invoked implicitly when a is awaited. 67 | /// 68 | /// The instance to place in the cache 69 | internal static void Release (ResultHolder result) 70 | => ReusableTaskMethodBuilder.Release (result); 71 | 72 | ReusableTask task; 73 | 74 | /// 75 | /// 76 | /// 77 | public ReusableTask Task => task; 78 | 79 | /// 80 | /// 81 | /// 82 | /// 83 | public void SetException (Exception e) 84 | { 85 | if (task.ResultHolder == null) 86 | task = new ReusableTask (ReusableTaskMethodBuilder.GetOrCreate ()); 87 | task.ResultHolder.SetException (e); 88 | } 89 | 90 | /// 91 | /// 92 | /// 93 | public void SetResult () 94 | { 95 | if (task.ResultHolder == null) 96 | task = ReusableTask.CompletedTask; 97 | else 98 | task.ResultHolder.SetResult (default); 99 | } 100 | 101 | /// 102 | /// 103 | /// 104 | /// 105 | /// 106 | /// 107 | /// 108 | public void AwaitOnCompleted (ref TAwaiter awaiter, ref TStateMachine stateMachine) 109 | where TAwaiter : INotifyCompletion 110 | where TStateMachine : IAsyncStateMachine 111 | { 112 | if (task.ResultHolder == null) { 113 | task = new ReusableTask (ReusableTaskMethodBuilder.GetOrCreate ()); 114 | task.ResultHolder.SyncContext = SynchronizationContext.Current; 115 | } 116 | 117 | ReusableTaskMethodBuilderCore.AwaitOnCompleted (ref awaiter, ref stateMachine); 118 | } 119 | 120 | /// 121 | /// 122 | /// 123 | /// 124 | /// 125 | /// 126 | /// 127 | public void AwaitUnsafeOnCompleted (ref TAwaiter awaiter, ref TStateMachine stateMachine) 128 | where TAwaiter : ICriticalNotifyCompletion 129 | where TStateMachine : IAsyncStateMachine 130 | { 131 | if (task.ResultHolder == null) { 132 | task = new ReusableTask (ReusableTaskMethodBuilder.GetOrCreate ()); 133 | task.ResultHolder.SyncContext = SynchronizationContext.Current; 134 | } 135 | 136 | ReusableTaskMethodBuilderCore.AwaitUnsafeOnCompleted (ref awaiter, ref stateMachine); 137 | } 138 | 139 | /// 140 | /// 141 | /// 142 | /// 143 | /// 144 | public void Start (ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine 145 | { 146 | stateMachine.MoveNext (); 147 | } 148 | 149 | /// 150 | /// 151 | /// 152 | /// 153 | public void SetStateMachine (IAsyncStateMachine stateMachine) 154 | { 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/ReusableTasks.Tests/AsyncBoundedQueueTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | using NUnit.Framework; 6 | 7 | namespace ReusableTasks.Tests 8 | { 9 | [TestFixture] 10 | public class AsyncProducerConsumerQueueTests 11 | { 12 | [Test] 13 | public void CancelBeforeAsynchronousDequeue () 14 | { 15 | var cts = new CancellationTokenSource (); 16 | var queue = new AsyncProducerConsumerQueue (1); 17 | 18 | cts.Cancel (); 19 | Assert.ThrowsAsync (() => queue.DequeueAsync (cts.Token).WithTimeout ("#1").AsTask ()); 20 | } 21 | 22 | [Test] 23 | public async Task CancelBeforeAsynchronousEnqueue () 24 | { 25 | var cts = new CancellationTokenSource (); 26 | var queue = new AsyncProducerConsumerQueue (1); 27 | await queue.EnqueueAsync (1); 28 | 29 | cts.Cancel (); 30 | Assert.ThrowsAsync (() => queue.EnqueueAsync (1, cts.Token).WithTimeout ("#1").AsTask ()); 31 | } 32 | 33 | [Test] 34 | public async Task CancelBeforeSynchronousDequeue () 35 | { 36 | var cts = new CancellationTokenSource (); 37 | var queue = new AsyncProducerConsumerQueue (1); 38 | await queue.EnqueueAsync (1); 39 | 40 | cts.Cancel (); 41 | Assert.ThrowsAsync (() => queue.DequeueAsync (cts.Token).WithTimeout ("#1").AsTask ()); 42 | } 43 | 44 | [Test] 45 | public void CancelBeforeSynchronousEnqueue () 46 | { 47 | var cts = new CancellationTokenSource (); 48 | var queue = new AsyncProducerConsumerQueue (1); 49 | 50 | cts.Cancel (); 51 | Assert.ThrowsAsync (() => queue.EnqueueAsync (1, cts.Token).WithTimeout ("#1").AsTask ()); 52 | } 53 | 54 | [Test] 55 | public void CancelWithPendingDequeue () 56 | { 57 | var cts = new CancellationTokenSource (); 58 | var queue = new AsyncProducerConsumerQueue (1); 59 | 60 | var task = queue.DequeueAsync (cts.Token).WithTimeout ("#1").AsTask (); 61 | cts.Cancel (); 62 | Assert.ThrowsAsync (() => task, "#1"); 63 | } 64 | 65 | [Test] 66 | public async Task CancelWithPendingEnqueue () 67 | { 68 | var cts = new CancellationTokenSource (); 69 | var queue = new AsyncProducerConsumerQueue (1); 70 | await queue.EnqueueAsync (10); 71 | 72 | var task = queue.EnqueueAsync (123, cts.Token).WithTimeout ("#1").AsTask (); 73 | cts.Cancel (); 74 | Assert.ThrowsAsync (() => task, "#1"); 75 | } 76 | 77 | [Test] 78 | public void CompleteWhilePendingDequeue () 79 | { 80 | var queue = new AsyncProducerConsumerQueue (2); 81 | var dequeue = queue.DequeueAsync (); 82 | queue.CompleteAdding (); 83 | Assert.ThrowsAsync (() => dequeue.AsTask (), "#1"); 84 | } 85 | 86 | [Test] 87 | public async Task CompleteWhilePendingEnqueue () 88 | { 89 | var queue = new AsyncProducerConsumerQueue (1); 90 | await queue.EnqueueAsync (5); 91 | var enqueueTask = queue.EnqueueAsync (6); 92 | 93 | queue.CompleteAdding (); 94 | Assert.AreEqual (5, await queue.DequeueAsync (), "#1"); 95 | 96 | await enqueueTask; 97 | Assert.AreEqual (6, await queue.DequeueAsync (), "#2"); 98 | } 99 | 100 | [Test] 101 | public void DequeueAfterComplete_Empty () 102 | { 103 | var queue = new AsyncProducerConsumerQueue (2); 104 | queue.CompleteAdding (); 105 | Assert.ThrowsAsync (() => queue.DequeueAsync ().AsTask (), "#1"); 106 | } 107 | 108 | [Test] 109 | public async Task DequeueAfterComplete_NotEmpty () 110 | { 111 | var queue = new AsyncProducerConsumerQueue (2); 112 | await queue.EnqueueAsync (5); 113 | queue.CompleteAdding (); 114 | Assert.AreEqual (5, await queue.DequeueAsync (), "#1"); 115 | } 116 | 117 | [Test] 118 | public async Task DequeueFirst_Bounded () 119 | { 120 | var queue = new AsyncProducerConsumerQueue (2); 121 | 122 | var task = queue.DequeueAsync (); 123 | Assert.IsFalse (task.IsCompleted, "#1"); 124 | 125 | await queue.EnqueueAsync (5); 126 | Assert.AreEqual (5, await task.WithTimeout ("#2")); 127 | } 128 | 129 | [Test] 130 | public async Task DequeueFirst_Unbounded () 131 | { 132 | var queue = new AsyncProducerConsumerQueue (0); 133 | 134 | var task = queue.DequeueAsync (); 135 | Assert.IsFalse (task.IsCompleted, "#1"); 136 | 137 | await queue.EnqueueAsync (5); 138 | Assert.AreEqual (5, await task.WithTimeout ("#2")); 139 | } 140 | 141 | [Test] 142 | public void EnqueueAfterComplete () 143 | { 144 | var queue = new AsyncProducerConsumerQueue (2); 145 | queue.CompleteAdding (); 146 | Assert.ThrowsAsync (() => queue.EnqueueAsync (5).AsTask (), "#1"); 147 | } 148 | 149 | [Test] 150 | public async Task EnqueueFirst () 151 | { 152 | var queue = new AsyncProducerConsumerQueue (2); 153 | 154 | await queue.EnqueueAsync (5).WithTimeout ("#1"); 155 | Assert.AreEqual (5, await queue.DequeueAsync (), "#2"); 156 | } 157 | 158 | [Test] 159 | public async Task EnqueueTooMany () 160 | { 161 | var queue = new AsyncProducerConsumerQueue (1); 162 | await queue.EnqueueAsync (5).WithTimeout ("#1"); 163 | 164 | var task2 = queue.EnqueueAsync (5); 165 | Assert.IsFalse (task2.IsCompleted, "#2"); 166 | 167 | await queue.DequeueAsync ().WithTimeout ("#3"); 168 | await task2.WithTimeout ("#4"); 169 | 170 | await queue.DequeueAsync ().WithTimeout ("#5"); 171 | } 172 | 173 | [Test] 174 | public async Task EmptyTwice () 175 | { 176 | var queue = new AsyncProducerConsumerQueue (1); 177 | await queue.EnqueueAsync (1); 178 | await queue.DequeueAsync (); 179 | await queue.EnqueueAsync (1); 180 | await queue.DequeueAsync (); 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/ReusableTasks/System.Runtime.CompilerServices/ReusableTaskMethodBuilder_T.cs: -------------------------------------------------------------------------------- 1 | // 2 | // ReusableTaskMethodBuilder_T.cs 3 | // 4 | // Authors: 5 | // Alan McGovern alan.mcgovern@gmail.com 6 | // 7 | // Copyright (C) 2019 Alan McGovern 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | 30 | #pragma warning disable RECS0108 // Warns about static fields in generic types 31 | 32 | using System.Collections.Generic; 33 | using System.Threading; 34 | 35 | using ReusableTasks; 36 | 37 | namespace System.Runtime.CompilerServices 38 | { 39 | /// 40 | /// Not intended to be used directly. 41 | /// 42 | public struct ReusableTaskMethodBuilder 43 | { 44 | static readonly Stack> Cache = new Stack> (); 45 | static readonly SimpleSpinLock CacheLock = new SimpleSpinLock (); 46 | 47 | /// 48 | /// The number of instances currently in the cache. 49 | /// 50 | public static int CacheCount => Cache.Count; 51 | 52 | /// 53 | /// Removes all instances from the cache. 54 | /// 55 | public static void ClearCache () 56 | { 57 | using (CacheLock.Enter ()) 58 | Cache.Clear (); 59 | } 60 | 61 | /// 62 | /// Not intended to be used directly. This method returns an object from the cache, or instantiates 63 | /// and returns a new object if the cache is empty. 64 | /// 65 | /// 66 | public static ReusableTaskMethodBuilder Create () 67 | => new ReusableTaskMethodBuilder (); 68 | 69 | internal static ResultHolder GetOrCreate () 70 | { 71 | using (CacheLock.Enter ()) 72 | return Cache.Count > 0 ? Cache.Pop () : new ResultHolder (); 73 | } 74 | 75 | /// 76 | /// Places the instance into the cache for re-use. This is invoked implicitly when a is awaited. 77 | /// 78 | /// The instance to place in the cache 79 | internal static void Release (ResultHolder result) 80 | { 81 | // This is always resettable, but sometimes cacheable. 82 | result.Reset (); 83 | if (result.Cacheable) { 84 | using (CacheLock.Enter ()) 85 | if (Cache.Count < ReusableTaskMethodBuilder.MaximumCacheSize) 86 | Cache.Push (result); 87 | } 88 | } 89 | 90 | ReusableTask task; 91 | 92 | /// 93 | /// 94 | /// 95 | public ReusableTask Task => task; 96 | 97 | /// 98 | /// 99 | /// 100 | /// 101 | public void SetException (Exception e) 102 | { 103 | if (task.ResultHolder == null) 104 | task = new ReusableTask (GetOrCreate ()); 105 | task.ResultHolder.SetException (e); 106 | } 107 | 108 | /// 109 | /// 110 | /// 111 | /// 112 | public void SetResult (T result) 113 | { 114 | if (task.ResultHolder == null) 115 | task = new ReusableTask (result); 116 | else 117 | task.ResultHolder.SetResult (result); 118 | } 119 | 120 | /// 121 | /// 122 | /// 123 | /// 124 | /// 125 | /// 126 | /// 127 | public void AwaitOnCompleted (ref TAwaiter awaiter, ref TStateMachine stateMachine) 128 | where TAwaiter : INotifyCompletion 129 | where TStateMachine : IAsyncStateMachine 130 | { 131 | if (task.ResultHolder == null) { 132 | task = new ReusableTask (GetOrCreate ()); 133 | task.ResultHolder.SyncContext = SynchronizationContext.Current; 134 | } 135 | 136 | ReusableTaskMethodBuilderCore.AwaitOnCompleted (ref awaiter, ref stateMachine); 137 | } 138 | 139 | /// 140 | /// 141 | /// 142 | /// 143 | /// 144 | /// 145 | /// 146 | public void AwaitUnsafeOnCompleted (ref TAwaiter awaiter, ref TStateMachine stateMachine) 147 | where TAwaiter : ICriticalNotifyCompletion 148 | where TStateMachine : IAsyncStateMachine 149 | { 150 | if (task.ResultHolder == null) { 151 | task = new ReusableTask (GetOrCreate ()); 152 | task.ResultHolder.SyncContext = SynchronizationContext.Current; 153 | } 154 | 155 | ReusableTaskMethodBuilderCore.AwaitUnsafeOnCompleted (ref awaiter, ref stateMachine); 156 | } 157 | 158 | /// 159 | /// 160 | /// 161 | /// 162 | /// 163 | public void Start (ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine 164 | { 165 | stateMachine.MoveNext (); 166 | } 167 | 168 | /// 169 | /// 170 | /// 171 | /// 172 | public void SetStateMachine (IAsyncStateMachine stateMachine) 173 | { 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/ReusableTasks/ReusableTasks/AsyncBoundedQueue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | 5 | namespace ReusableTasks 6 | { 7 | /// 8 | /// This is a zero allocation collection which implements the Producer-Consumer pattern. It 9 | /// supports a single producer and single consumer. The capacity can be bounded, or unbounded. 10 | /// 11 | /// 12 | public class AsyncProducerConsumerQueue 13 | { 14 | Action cancelDequeuedCallback; 15 | Action cancelEnqueuedCallback; 16 | 17 | Action CancelDequeuedCallback { 18 | get => cancelDequeuedCallback ?? (cancelDequeuedCallback = CancelDequeued); 19 | } 20 | 21 | Action CancelEnqueuedCallback { 22 | get => cancelEnqueuedCallback ?? (cancelEnqueuedCallback = CancelEnqueued); 23 | } 24 | 25 | /// 26 | /// The maximum number of work items which can be queued. A value of zero means there is 27 | /// no limit. 28 | /// 29 | public int Capacity { get; } 30 | 31 | /// 32 | /// The number of items in the queue. 33 | /// 34 | public int Count => Queue.Count; 35 | 36 | /// 37 | /// Returns true if no more items will be added to the queue. 38 | /// 39 | public bool IsAddingCompleted { get; private set; } 40 | 41 | /// 42 | /// Returns true if the capacity is greater than zero, indicating a limited number of 43 | /// items can be queued at any one time. 44 | /// 45 | public bool IsBounded => Capacity > 0; 46 | 47 | ReusableTaskCompletionSource Dequeued { get; } 48 | ReusableTaskCompletionSource Enqueued { get; } 49 | Queue Queue { get; } 50 | SimpleSpinLock QueueLock { get; } 51 | 52 | /// 53 | /// Creates a new instance of . 54 | /// 55 | /// A value of 0 means the collection has an unbounded size. A value greater 56 | /// than zero represents the maximum number of items which can be queued. 57 | public AsyncProducerConsumerQueue (int capacity) 58 | { 59 | Capacity = capacity; 60 | Dequeued = new ReusableTaskCompletionSource (true); 61 | Enqueued = new ReusableTaskCompletionSource (true); 62 | Queue = new Queue (); 63 | QueueLock = new SimpleSpinLock (); 64 | } 65 | 66 | void CancelDequeued () => Dequeued.TrySetCanceled (); 67 | 68 | void CancelEnqueued () => Enqueued.TrySetCanceled (); 69 | 70 | /// 71 | /// Sets to true and interrupts any pending 72 | /// calls if the collection is already empty. Future calls to will throw 73 | /// an . 74 | /// 75 | public void CompleteAdding () 76 | { 77 | using (QueueLock.Enter ()) 78 | IsAddingCompleted = true; 79 | Enqueued.TrySetResult (true); 80 | } 81 | 82 | /// 83 | /// If an item has already been enqueued, then it will be dequeued and returned synchronously. Otherwise an 84 | /// item must be enqueued before this will return. 85 | /// will be added. 86 | /// /// 87 | /// 88 | public ReusableTask DequeueAsync () 89 | => DequeueAsync (CancellationToken.None); 90 | 91 | /// 92 | /// If an item has already been enqueued, then it will be dequeued and returned synchronously. Otherwise an 93 | /// item must be enqueued before this will return. 94 | /// will be added. 95 | /// /// 96 | /// The token used to cancel the pending dequeue. 97 | /// 98 | public async ReusableTask DequeueAsync (CancellationToken token) 99 | { 100 | while (true) { 101 | using (QueueLock.Enter ()) { 102 | if (Queue.Count == 0 && IsAddingCompleted) 103 | throw new InvalidOperationException ("This queue has been marked as complete, so no further items can be added."); 104 | 105 | if (Queue.Count > 0) { 106 | token.ThrowIfCancellationRequested (); 107 | var result = Queue.Dequeue (); 108 | if (Queue.Count == Capacity - 1) 109 | Dequeued.TrySetResult (true); 110 | return result; 111 | } 112 | } 113 | 114 | using (var registration = token == CancellationToken.None ? default : token.Register (CancelEnqueuedCallback)) 115 | await Enqueued.Task.ConfigureAwait (false); 116 | } 117 | } 118 | 119 | /// 120 | /// The new item will be enqueued synchronously if the number of items already 121 | /// enqueued is less than the capacity. Otherwise an item must be dequeued before the new item 122 | /// will be added. 123 | /// /// 124 | /// The item to enqueue 125 | /// 126 | public ReusableTask EnqueueAsync (T value) 127 | => EnqueueAsync (value, CancellationToken.None); 128 | 129 | /// 130 | /// The new item will be enqueued synchronously if the number of items already 131 | /// enqueued is less than the capacity. Otherwise an item must be dequeued before the new item 132 | /// will be added. 133 | /// /// 134 | /// The item to enqueue 135 | /// The token used to cancel the pending enqueue. 136 | /// 137 | public async ReusableTask EnqueueAsync (T value, CancellationToken token) 138 | { 139 | if (IsAddingCompleted) 140 | throw new InvalidOperationException ("This queue has been marked as complete, so no further items can be added."); 141 | 142 | while (true) { 143 | using (QueueLock.Enter ()) { 144 | if (Queue.Count < Capacity || !IsBounded) { 145 | token.ThrowIfCancellationRequested (); 146 | Queue.Enqueue (value); 147 | if (Queue.Count == 1) 148 | Enqueued.TrySetResult (true); 149 | return; 150 | } 151 | } 152 | using (var registration = token == CancellationToken.None ? default : token.Register (CancelDequeuedCallback)) 153 | await Dequeued.Task.ConfigureAwait (false); 154 | } 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/ReusableTasks.Tests/ReusableTask_WithSyncContext_Tests.cs: -------------------------------------------------------------------------------- 1 | // 2 | // ReusableTask_WithSyncContext_Tests.cs 3 | // 4 | // Authors: 5 | // Alan McGovern alan.mcgovern@gmail.com 6 | // 7 | // Copyright (C) 2019 Alan McGovern 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | 30 | using System; 31 | using System.Threading; 32 | using System.Threading.Tasks; 33 | 34 | using NUnit.Framework; 35 | 36 | namespace ReusableTasks.Tests 37 | { 38 | [TestFixture] 39 | public class ReusableTask_WithSyncContext_Tests 40 | { 41 | TaskCompletionSource delayingTask; 42 | 43 | [SetUp] 44 | public void Setup () 45 | { 46 | delayingTask = new TaskCompletionSource (TaskCreationOptions.RunContinuationsAsynchronously); 47 | var cancellation = new CancellationTokenSource (TimeSpan.FromSeconds (10)); 48 | cancellation.Token.Register (() => delayingTask.TrySetCanceled ()); 49 | } 50 | 51 | [Test] 52 | public async Task SyncMethod_ConfigureTrue () 53 | { 54 | await TestSynchronizationContext.Instance; 55 | TestSynchronizationContext.Instance.ResetCounts (); 56 | 57 | await First (); 58 | Assert.AreEqual (0, TestSynchronizationContext.Instance.Posted, "#1"); 59 | } 60 | 61 | [Test] 62 | public async Task SyncMethod_ConfigureFalse () 63 | { 64 | await TestSynchronizationContext.Instance; 65 | TestSynchronizationContext.Instance.ResetCounts (); 66 | 67 | var task = Second (); 68 | delayingTask.SetResult (1); 69 | await task; 70 | 71 | Assert.AreEqual (0, TestSynchronizationContext.Instance.Posted, "#1"); 72 | } 73 | 74 | [Test] 75 | public async Task AsyncMethod_ConfigureTrue () 76 | { 77 | await TestSynchronizationContext.Instance; 78 | TestSynchronizationContext.Instance.ResetCounts (); 79 | 80 | var task = Third ().ConfigureAwait (false); 81 | delayingTask.SetResult (1); 82 | await task; 83 | 84 | Assert.AreEqual (1, TestSynchronizationContext.Instance.Posted, "#1"); 85 | } 86 | 87 | [Test] 88 | public async Task AsyncMethod_ConfigureFalse () 89 | { 90 | await TestSynchronizationContext.Instance; 91 | TestSynchronizationContext.Instance.ResetCounts (); 92 | 93 | var task = Fourth ().ConfigureAwait (false); 94 | delayingTask.SetResult (1); 95 | await task; 96 | 97 | Assert.AreEqual (1, TestSynchronizationContext.Instance.Posted, "#1"); 98 | } 99 | 100 | [Test] 101 | public async Task AsyncMethod_ConfigureFalse_ConfigureTrue () 102 | { 103 | await TestSynchronizationContext.Instance; 104 | TestSynchronizationContext.Instance.ResetCounts (); 105 | 106 | var task = Fifth ().ConfigureAwait (false); 107 | delayingTask.SetResult (1); 108 | await task; 109 | 110 | Assert.AreNotEqual (TestSynchronizationContext.Instance, SynchronizationContext.Current, "#2"); 111 | Assert.AreEqual (1, TestSynchronizationContext.Instance.Posted, "#1"); 112 | } 113 | 114 | [Test] 115 | public async Task AsyncMethod_ConfigureFalse_ConfigureFalse () 116 | { 117 | await TestSynchronizationContext.Instance; 118 | TestSynchronizationContext.Instance.ResetCounts (); 119 | 120 | var task = Sixth ().ConfigureAwait (false); 121 | delayingTask.SetResult (1); 122 | await task; 123 | 124 | Assert.AreEqual (0, TestSynchronizationContext.Instance.Posted, "#1"); 125 | } 126 | 127 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously 128 | async ReusableTask EmptyMethod () 129 | { 130 | } 131 | #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously 132 | 133 | async ReusableTask DelayMethodCapture () 134 | { 135 | await delayingTask.Task.ConfigureAwait (true); 136 | Assert.AreEqual (TestSynchronizationContext.Instance, SynchronizationContext.Current, "#1"); 137 | Assert.IsFalse (Thread.CurrentThread.IsThreadPoolThread, "#2"); 138 | } 139 | 140 | async ReusableTask DelayMethodDoNotCapture () 141 | { 142 | await delayingTask.Task.ConfigureAwait (false); 143 | 144 | Assert.AreEqual (null, SynchronizationContext.Current, "#1"); 145 | Assert.IsTrue (Thread.CurrentThread.IsThreadPoolThread, "#2"); 146 | } 147 | 148 | async ReusableTask First () 149 | { 150 | await EmptyMethod ().ConfigureAwait (true); 151 | Assert.AreEqual (TestSynchronizationContext.Instance, SynchronizationContext.Current, "#1"); 152 | } 153 | 154 | async ReusableTask Second () 155 | { 156 | await EmptyMethod ().ConfigureAwait (false); 157 | Assert.AreEqual (TestSynchronizationContext.Instance, SynchronizationContext.Current, "#1"); 158 | } 159 | 160 | async ReusableTask Third () 161 | { 162 | await DelayMethodCapture ().ConfigureAwait (true); 163 | Assert.AreEqual (TestSynchronizationContext.Instance, SynchronizationContext.Current, "#1"); 164 | } 165 | 166 | async ReusableTask Fourth () 167 | { 168 | await DelayMethodCapture ().ConfigureAwait (false); 169 | Assert.AreEqual (null, SynchronizationContext.Current, "#1"); 170 | } 171 | 172 | async ReusableTask Fifth () 173 | { 174 | await DelayMethodDoNotCapture ().ConfigureAwait (true); 175 | Assert.AreEqual (TestSynchronizationContext.Instance, SynchronizationContext.Current, "#1"); 176 | } 177 | 178 | async ReusableTask Sixth () 179 | { 180 | await DelayMethodDoNotCapture ().ConfigureAwait (false); 181 | Assert.AreEqual (null, SynchronizationContext.Current, "#1"); 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/.editorconfig: -------------------------------------------------------------------------------- 1 | # Remove the line below if you want to inherit .editorconfig settings from higher directories 2 | root = true 3 | 4 | [*] 5 | #### Core EditorConfig Options #### 6 | # Indentation and spacing 7 | indent_size = 4 8 | indent_style = space 9 | tab_width = 4 10 | trim_trailing_whitespace = true 11 | 12 | # New line preferences 13 | end_of_line = crlf 14 | insert_final_newline = true 15 | 16 | # MSBuild related files 17 | [*.{csproj,props,targets}] 18 | indent_size = 2 19 | 20 | # C# files 21 | [*.cs] 22 | 23 | #### .NET Coding Conventions #### 24 | 25 | # Organize usings 26 | dotnet_separate_import_directive_groups = true 27 | dotnet_sort_system_directives_first = true 28 | 29 | # this. and Me. preferences 30 | dotnet_style_qualification_for_event = false:silent 31 | dotnet_style_qualification_for_field = false:silent 32 | dotnet_style_qualification_for_method = false:silent 33 | dotnet_style_qualification_for_property = false:silent 34 | 35 | # Language keywords vs BCL types preferences 36 | dotnet_style_predefined_type_for_locals_parameters_members = true:silent 37 | dotnet_style_predefined_type_for_member_access = true:silent 38 | 39 | # Parentheses preferences 40 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent 41 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent 42 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent 43 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent 44 | 45 | # Modifier preferences 46 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent 47 | 48 | # Expression-level preferences 49 | dotnet_style_coalesce_expression = true:suggestion 50 | dotnet_style_collection_initializer = true:suggestion 51 | dotnet_style_explicit_tuple_names = true:suggestion 52 | dotnet_style_null_propagation = true:suggestion 53 | dotnet_style_object_initializer = true:suggestion 54 | dotnet_style_prefer_auto_properties = true:silent 55 | dotnet_style_prefer_compound_assignment = true:suggestion 56 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 57 | dotnet_style_prefer_conditional_expression_over_return = true:silent 58 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 59 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 60 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion 61 | dotnet_style_require_accessibility_modifiers = omit_if_default:silent 62 | 63 | # Field preferences 64 | dotnet_style_readonly_field = true:suggestion 65 | 66 | # Parameter preferences 67 | dotnet_code_quality_unused_parameters = all:suggestion 68 | 69 | #### C# Coding Conventions #### 70 | 71 | # var preferences 72 | csharp_style_var_elsewhere = false:silent 73 | csharp_style_var_for_built_in_types = false:silent 74 | csharp_style_var_when_type_is_apparent = true:silent 75 | 76 | # Expression-bodied members 77 | csharp_style_expression_bodied_accessors = true:silent 78 | csharp_style_expression_bodied_constructors = false:silent 79 | csharp_style_expression_bodied_indexers = true:silent 80 | csharp_style_expression_bodied_lambdas = true:silent 81 | csharp_style_expression_bodied_local_functions = false:silent 82 | csharp_style_expression_bodied_methods = false:silent 83 | csharp_style_expression_bodied_operators = false:silent 84 | csharp_style_expression_bodied_properties = true:silent 85 | 86 | # Pattern matching preferences 87 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 88 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 89 | csharp_style_prefer_switch_expression = true:suggestion 90 | 91 | # Null-checking preferences 92 | csharp_style_conditional_delegate_call = true:suggestion 93 | 94 | # Modifier preferences 95 | csharp_prefer_static_local_function = true:suggestion 96 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async 97 | 98 | # Code-block preferences 99 | csharp_prefer_braces = false:suggestion 100 | csharp_prefer_simple_using_statement = true:suggestion 101 | 102 | # Expression-level preferences 103 | csharp_prefer_simple_default_expression = true:suggestion 104 | csharp_style_deconstructed_variable_declaration = true:suggestion 105 | csharp_style_inlined_variable_declaration = true:suggestion 106 | csharp_style_pattern_local_over_anonymous_function = true:suggestion 107 | csharp_style_prefer_index_operator = true:suggestion 108 | csharp_style_prefer_range_operator = true:suggestion 109 | csharp_style_throw_expression = true:suggestion 110 | csharp_style_unused_value_assignment_preference = discard_variable:suggestion 111 | csharp_style_unused_value_expression_statement_preference = discard_variable:silent 112 | 113 | # 'using' directive preferences 114 | csharp_using_directive_placement = outside_namespace:silent 115 | 116 | #### C# Formatting Rules #### 117 | 118 | # New line preferences 119 | csharp_new_line_before_catch = false 120 | csharp_new_line_before_else = false 121 | csharp_new_line_before_finally = false 122 | csharp_new_line_before_members_in_anonymous_types = true 123 | csharp_new_line_before_members_in_object_initializers = true 124 | csharp_new_line_before_open_brace = methods,types 125 | csharp_new_line_between_query_expression_clauses = true 126 | 127 | # Indentation preferences 128 | csharp_indent_block_contents = true 129 | csharp_indent_braces = false 130 | csharp_indent_case_contents = true 131 | csharp_indent_case_contents_when_block = false 132 | csharp_indent_labels = one_less_than_current 133 | csharp_indent_switch_labels = true 134 | 135 | # Space preferences 136 | csharp_space_after_cast = true 137 | csharp_space_after_colon_in_inheritance_clause = true 138 | csharp_space_after_comma = true 139 | csharp_space_after_dot = false 140 | csharp_space_after_keywords_in_control_flow_statements = true 141 | csharp_space_after_semicolon_in_for_statement = true 142 | csharp_space_around_binary_operators = before_and_after 143 | csharp_space_around_declaration_statements = false 144 | csharp_space_before_colon_in_inheritance_clause = true 145 | csharp_space_before_comma = false 146 | csharp_space_before_dot = false 147 | csharp_space_before_open_square_brackets = false 148 | csharp_space_before_semicolon_in_for_statement = false 149 | csharp_space_between_empty_square_brackets = false 150 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 151 | csharp_space_between_method_call_name_and_opening_parenthesis = true 152 | csharp_space_between_method_call_parameter_list_parentheses = false 153 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 154 | csharp_space_between_method_declaration_name_and_open_parenthesis = true 155 | csharp_space_between_method_declaration_parameter_list_parentheses = false 156 | csharp_space_between_parentheses = false 157 | csharp_space_between_square_brackets = false 158 | 159 | # Wrapping preferences 160 | csharp_preserve_single_line_blocks = true 161 | csharp_preserve_single_line_statements = false 162 | 163 | #### Naming styles #### 164 | 165 | # Naming rules 166 | 167 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion 168 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 169 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 170 | 171 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion 172 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 173 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 174 | 175 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion 176 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members 177 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case 178 | 179 | # Symbol specifications 180 | 181 | dotnet_naming_symbols.interface.applicable_kinds = interface 182 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 183 | dotnet_naming_symbols.interface.required_modifiers = 184 | 185 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum 186 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 187 | dotnet_naming_symbols.types.required_modifiers = 188 | 189 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method 190 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 191 | dotnet_naming_symbols.non_field_members.required_modifiers = 192 | 193 | # Naming styles 194 | 195 | dotnet_naming_style.pascal_case.required_prefix = 196 | dotnet_naming_style.pascal_case.required_suffix = 197 | dotnet_naming_style.pascal_case.word_separator = 198 | dotnet_naming_style.pascal_case.capitalization = pascal_case 199 | 200 | dotnet_naming_style.begins_with_i.required_prefix = I 201 | dotnet_naming_style.begins_with_i.required_suffix = 202 | dotnet_naming_style.begins_with_i.word_separator = 203 | dotnet_naming_style.begins_with_i.capitalization = pascal_case 204 | -------------------------------------------------------------------------------- /src/ReusableTasks/System.Runtime.CompilerServices/ResultHolder_T.cs: -------------------------------------------------------------------------------- 1 | // 2 | // ResultHolder.cs 3 | // 4 | // Authors: 5 | // Alan McGovern alan.mcgovern@gmail.com 6 | // 7 | // Copyright (C) 2019 Alan McGovern 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | 30 | using System.Diagnostics; 31 | using System.Threading; 32 | using System.Threading.Tasks; 33 | 34 | using ReusableTasks; 35 | 36 | namespace System.Runtime.CompilerServices 37 | { 38 | class ResultHolder 39 | { 40 | protected const int CacheableFlag = 1 << 29; 41 | protected const int ForceAsynchronousContinuationFlag = 1 << 30; 42 | protected const int SettingValueFlag = 1 << 31; 43 | // The top 3 bits are reserved for various flags, the rest is used for the unique ID. 44 | protected const int IdMask = ~(CacheableFlag | ForceAsynchronousContinuationFlag | SettingValueFlag); 45 | // When resetting the instance we want to retain the 'Cacheable' and 'ForceAsync' flags. 46 | protected const int RetainedFlags = CacheableFlag | ForceAsynchronousContinuationFlag; 47 | 48 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 49 | internal static void Invoker (object state) 50 | { 51 | Debug.Assert (state is Action || state is StateMachineCache); 52 | if (state is Action action) 53 | action.Invoke (); 54 | else 55 | Unsafe.As (state).OnCompleted (); 56 | } 57 | 58 | // This is a string purely to aid debugging. it's obvious when this value is set 59 | protected static object HasValueSentinel = "has_value_sentinal"; 60 | 61 | protected static readonly SendOrPostCallback InvokeOnContext = Invoker; 62 | protected static readonly WaitCallback InvokeOnThreadPool = Invoker; 63 | 64 | protected object continuation; 65 | public SynchronizationContext SyncContext; 66 | protected int state; 67 | 68 | 69 | public bool Cacheable 70 | => (state & CacheableFlag) == CacheableFlag; 71 | 72 | public object Continuation { 73 | get => continuation; 74 | set { 75 | // If 'continuation' is set to 'null' then we have not yet set a value. In this 76 | // scenario we should place the compiler-supplied continuation in the field so 77 | // that when a value is set we can directly invoke the continuation. 78 | var sentinel = HasValueSentinel; 79 | var action = Interlocked.CompareExchange (ref continuation, value, null); 80 | if (action == sentinel) { 81 | // A non-null action means that the 'HasValueSentinel' was set on the field. 82 | // This indicates a value has already been set, so we can execute the 83 | // compiler-supplied continuation immediately. 84 | if (Interlocked.CompareExchange (ref continuation, value, sentinel) != sentinel) 85 | throw new InvalidTaskReuseException ("A mismatch was detected when attempting to invoke the continuation. This typically means the ReusableTask was awaited twice concurrently. If you need to do this, convert the ReusableTask to a Task before awaiting."); 86 | TryInvoke (value); 87 | } else if (action != null) { 88 | throw new InvalidTaskReuseException ("A mismatch was detected between the ResuableTask and its Result source. This typically means the ReusableTask was awaited twice concurrently. If you need to do this, convert the ReusableTask to a Task before awaiting."); 89 | } 90 | } 91 | } 92 | 93 | public Exception Exception { 94 | get; protected set; 95 | } 96 | 97 | public bool ForceAsynchronousContinuation { 98 | get => (state & ForceAsynchronousContinuationFlag) == ForceAsynchronousContinuationFlag; 99 | set { 100 | if (value) 101 | state |= ForceAsynchronousContinuationFlag; 102 | else 103 | state &= ~ForceAsynchronousContinuationFlag; 104 | } 105 | } 106 | 107 | /// 108 | /// The compiler/runtime uses this to check whether or not the awaitable can 109 | /// be completed synchronously or asynchronously. If this property is checked 110 | /// and 'false' is returned, then 'INotifyCompletion.OnCompleted' will be invoked 111 | /// with the delegate we need to asynchronously invoke. If it returns true then 112 | /// the compiler/runtime will go ahead and invoke the continuation itself. 113 | /// 114 | public bool HasValue 115 | => continuation == HasValueSentinel; 116 | 117 | public int Id => state & IdMask; 118 | 119 | protected void TryInvoke (object callback) 120 | { 121 | // If we are supposed to execute on the captured sync context, use it. Otherwise 122 | // we should ensure the continuation executes on the threadpool. If the user has 123 | // created a dedicated thread (or whatever) we do not want to be executing on it. 124 | if (SyncContext == null && Thread.CurrentThread.IsThreadPoolThread && !ForceAsynchronousContinuation) 125 | Invoker (callback); 126 | else if (SyncContext != null && SynchronizationContext.Current == SyncContext && !ForceAsynchronousContinuation) 127 | Invoker (callback); 128 | else if (SyncContext != null) 129 | SyncContext.Post (InvokeOnContext, callback); 130 | else 131 | #if !NETSTANDARD2_0 && !NETSTANDARD2_1 132 | ThreadPool.UnsafeQueueUserWorkItem (ActionWorkItem.GetOrCreate (callback), false); 133 | #else 134 | ThreadPool.UnsafeQueueUserWorkItem (InvokeOnThreadPool, callback); 135 | #endif 136 | } 137 | } 138 | 139 | /// 140 | /// Not intended to be used directly. 141 | /// 142 | class ResultHolder : ResultHolder 143 | { 144 | T Value { get; set; } 145 | 146 | public ResultHolder () 147 | : this (true, false) 148 | { 149 | } 150 | 151 | public ResultHolder (bool cacheable, bool forceAsynchronousContinuation) 152 | { 153 | state |= cacheable ? CacheableFlag : 0; 154 | ForceAsynchronousContinuation = forceAsynchronousContinuation; 155 | } 156 | 157 | public T GetResult () 158 | => Value; 159 | 160 | public void Reset () 161 | { 162 | continuation = null; 163 | Exception = null; 164 | SyncContext = null; 165 | Value = default; 166 | 167 | var retained = state & RetainedFlags; 168 | Interlocked.Exchange (ref state, ((state + 1) & IdMask) | retained); 169 | } 170 | 171 | public void SetCanceled () 172 | { 173 | if (!TrySetCanceled ()) 174 | throw new InvalidOperationException ("A result has already been set on this object"); 175 | } 176 | 177 | public void SetException (Exception exception) 178 | { 179 | if (!TrySetException (exception)) 180 | throw new InvalidOperationException ("A result has already been set on this object"); 181 | } 182 | 183 | public void SetResult (T result) 184 | { 185 | if (!TrySetResult (result)) 186 | throw new InvalidOperationException ("A result has already been set on this object"); 187 | } 188 | 189 | public bool TrySetCanceled () 190 | => TrySetExceptionOrResult (new TaskCanceledException (), default); 191 | 192 | public bool TrySetException (Exception exception) 193 | => TrySetExceptionOrResult (exception, default); 194 | 195 | public bool TrySetResult (T result) 196 | => TrySetExceptionOrResult (null, result); 197 | 198 | bool TrySetExceptionOrResult (Exception exception, T result) 199 | { 200 | var originalState = state; 201 | if ((originalState & SettingValueFlag) == SettingValueFlag) 202 | return false; 203 | 204 | if (Interlocked.CompareExchange (ref state, originalState | SettingValueFlag, originalState) != originalState) 205 | return false; 206 | 207 | // Set the exception/value and update the state 208 | Exception = exception; 209 | Value = result; 210 | 211 | // If 'continuation' is set to 'null' then we have not yet set a continuation. 212 | // In this scenario, set the continuation to a value signifying the result is now available. 213 | var continuation = Interlocked.CompareExchange (ref base.continuation, HasValueSentinel, null); 214 | if (continuation != null) { 215 | // This means the value returned by the CompareExchange was the continuation passed by the 216 | // compiler, so we can directly execute it now that we have set a value. 217 | TryInvoke (continuation); 218 | } 219 | return true; 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/ReusableTasks.Tests/ReusableTaskTests.cs: -------------------------------------------------------------------------------- 1 | // 2 | // ReusableTaskTests.cs 3 | // 4 | // Authors: 5 | // Alan McGovern alan.mcgovern@gmail.com 6 | // 7 | // Copyright (C) 2019 Alan McGovern 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | 30 | #pragma warning disable IDE0062 // Make local function 'static' 31 | 32 | using System; 33 | using System.Runtime.CompilerServices; 34 | using System.Threading; 35 | using System.Threading.Tasks; 36 | 37 | using NUnit.Framework; 38 | 39 | namespace ReusableTasks.Tests 40 | { 41 | [TestFixture] 42 | public class ReusableTaskTests 43 | { 44 | SynchronizationContext OriginalContext; 45 | 46 | [SetUp] 47 | public void Setup () 48 | { 49 | TestSynchronizationContext.Instance.ResetCounts (); 50 | OriginalContext = SynchronizationContext.Current; 51 | ReusableTaskMethodBuilder.ClearCache (); 52 | } 53 | 54 | [TearDown] 55 | public void Teardown () 56 | { 57 | SynchronizationContext.SetSynchronizationContext (OriginalContext); 58 | } 59 | 60 | [Test] 61 | public async Task AsTask () 62 | { 63 | async ReusableTask Test () 64 | { 65 | await Task.Delay (1); 66 | } 67 | 68 | var task = Test ().AsTask (); 69 | await task; 70 | await task; 71 | await task; 72 | 73 | Assert.AreEqual (1, ReusableTaskMethodBuilder.CacheCount, "#4"); 74 | } 75 | 76 | [Test] 77 | public void Asynchronous_AwaiterGetResultTwice () 78 | { 79 | async ReusableTask Test () 80 | { 81 | await Task.Yield (); 82 | } 83 | 84 | var task = Test (); 85 | var awaiter = task.GetAwaiter (); 86 | awaiter.GetResult (); 87 | Assert.Throws (() => awaiter.GetResult (), "#1"); 88 | } 89 | 90 | [Test] 91 | public void Asynchronous_AwaiterOnCompletedTwice () 92 | { 93 | async ReusableTask Test () 94 | { 95 | await Task.Yield (); 96 | } 97 | 98 | var task = Test (); 99 | var awaiter = task.GetAwaiter (); 100 | awaiter.OnCompleted (() => { }); 101 | Assert.Throws (() => awaiter.OnCompleted (() => { }), "#1"); 102 | } 103 | 104 | [Test] 105 | public async Task Asynchronous_ConfigureAwaitFalse () 106 | { 107 | var context = TestSynchronizationContext.Instance; 108 | SynchronizationContext.SetSynchronizationContext (context); 109 | 110 | async ReusableTask Test () 111 | { 112 | await Task.Delay (1).ConfigureAwait (false); 113 | await Task.Delay (1).ConfigureAwait (false); 114 | } 115 | 116 | await Test ().ConfigureAwait (false); 117 | Assert.AreEqual (0, context.Posted + context.Sent, "#1"); 118 | } 119 | 120 | [Test] 121 | public async Task Asynchronous_ConfigureAwaitTrue () 122 | { 123 | var context = TestSynchronizationContext.Instance; 124 | SynchronizationContext.SetSynchronizationContext (context); 125 | 126 | async ReusableTask Test () 127 | { 128 | await Task.Delay (1).ConfigureAwait (true); 129 | await Task.Delay (1).ConfigureAwait (true); 130 | GC.KeepAlive (this); 131 | } 132 | 133 | await Test ().ConfigureAwait (true); 134 | Assert.AreEqual (2, context.Posted + context.Sent, "#1"); 135 | } 136 | 137 | [Test] 138 | public void Asynchronous_Exception () 139 | { 140 | async ReusableTask Test () 141 | { 142 | await Task.Yield (); 143 | throw new TimeoutException (); 144 | } 145 | 146 | Assert.ThrowsAsync (async () => await Test ()); 147 | Assert.AreEqual (1, ReusableTaskMethodBuilder.CacheCount, "#1"); 148 | } 149 | 150 | [Test] 151 | public async Task Asynchronous_IsCompleted () 152 | { 153 | var firstWaiter = new SemaphoreSlim (0, 1); 154 | var secondWaiter = new SemaphoreSlim (0, 1); 155 | 156 | async ReusableTask Test () 157 | { 158 | firstWaiter.Release (); 159 | await secondWaiter.WaitAsync (); 160 | } 161 | 162 | var task = Test (); 163 | await firstWaiter.WaitAsync (); 164 | Assert.IsFalse (task.IsCompleted, "#1"); 165 | 166 | secondWaiter.Release (); 167 | int i = 0; 168 | while (!task.IsCompleted && i++ < 1000) 169 | await Task.Yield (); 170 | 171 | Assert.IsTrue (i < 1000, "#2"); 172 | Assert.IsTrue (task.IsCompleted, "#3"); 173 | } 174 | 175 | [Test] 176 | public async Task Asynchronous_IsCompleted_ThenAwait () 177 | { 178 | async ReusableTask Test () 179 | { 180 | await Task.Yield (); 181 | } 182 | 183 | var task = Test (); 184 | await task; 185 | Assert.IsFalse (task.IsCompleted, "#1"); 186 | } 187 | 188 | [Test] 189 | public async Task Asynchronous_ThreeConcurrent () 190 | { 191 | async ReusableTask Test () 192 | { 193 | await Task.Yield (); 194 | } 195 | 196 | var t1 = Test (); 197 | var t2 = Test (); 198 | var t3 = Test (); 199 | 200 | await t1; 201 | Assert.AreEqual (1, ReusableTaskMethodBuilder.CacheCount, "#1"); 202 | await t2; 203 | Assert.AreEqual (2, ReusableTaskMethodBuilder.CacheCount, "#2"); 204 | await t3; 205 | Assert.AreEqual (3, ReusableTaskMethodBuilder.CacheCount, "#3"); 206 | } 207 | 208 | [Test] 209 | public async Task Asynchronous_ThreeSequential () 210 | { 211 | async ReusableTask Test () 212 | { 213 | await Task.Yield (); 214 | } 215 | 216 | await Test (); 217 | await Test (); 218 | await Test (); 219 | 220 | Assert.AreEqual (1, ReusableTaskMethodBuilder.CacheCount, "#1"); 221 | } 222 | 223 | [Test] 224 | public async Task Asynchronous_ValidateFastPath () 225 | { 226 | // Validate the optimisation where ReusableTaskAwaiter fast-paths to ResultHolder 227 | async ReusableTask Test (int count) 228 | { 229 | await Task.Yield (); 230 | if (count > 0) 231 | await Test (count - 1); 232 | } 233 | 234 | await Test (5); 235 | 236 | Assert.AreEqual (6, ReusableTaskMethodBuilder.CacheCount, "#1"); 237 | } 238 | 239 | [Test] 240 | public async Task CompletedTask () 241 | { 242 | await ReusableTask.CompletedTask; 243 | await ReusableTask.CompletedTask.ConfigureAwait (false); 244 | await ReusableTask.CompletedTask.ConfigureAwait (true); 245 | ReusableTask.CompletedTask.GetAwaiter ().GetResult (); 246 | Assert.IsTrue (ReusableTask.CompletedTask.IsCompleted); 247 | Assert.IsTrue (ReusableTask.CompletedTask.GetAwaiter ().IsCompleted); 248 | } 249 | 250 | [Test] 251 | public void Synchronous_Exception () 252 | { 253 | #pragma warning disable CS1998 // Make local function 'static' 254 | async ReusableTask Test () 255 | { 256 | throw new TimeoutException (); 257 | } 258 | #pragma warning restore CS1998 // Make local function 'static' 259 | 260 | Assert.ThrowsAsync (async () => await Test ()); 261 | Assert.AreEqual (1, ReusableTaskMethodBuilder.CacheCount, "#1"); 262 | } 263 | 264 | [Test] 265 | public async Task FromResult_TwiceSequential () 266 | { 267 | var task = ReusableTask.FromResult (5); 268 | Assert.AreEqual (0, ReusableTaskMethodBuilder.CacheCount); 269 | Assert.IsTrue (task.IsCompleted); 270 | Assert.AreEqual (5, await task); 271 | 272 | Assert.AreEqual (15, await ReusableTask.FromResult (15)); 273 | } 274 | 275 | [Test] 276 | public async Task FromResult_TwiceConcurrent () 277 | { 278 | var task1 = ReusableTask.FromResult (4); 279 | var task2 = ReusableTask.FromResult (14); 280 | 281 | Assert.AreEqual (14, await task2); 282 | Assert.AreEqual (4, await task1); 283 | } 284 | 285 | [Test] 286 | public void Synchronous_IsCompleted () 287 | { 288 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously 289 | async ReusableTask Test () 290 | { 291 | } 292 | #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously 293 | 294 | var task = Test (); 295 | Assert.IsTrue (task.IsCompleted, "#1"); 296 | } 297 | 298 | [Test] 299 | public async Task Synchronous_IsCompleted_ThenAwait () 300 | { 301 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously 302 | async ReusableTask Test () 303 | { 304 | } 305 | #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously 306 | 307 | var task = Test (); 308 | await task; 309 | Assert.IsTrue (task.IsCompleted, "#1"); 310 | } 311 | 312 | [Test] 313 | public async Task Synchronous_ThreeConcurrent () 314 | { 315 | #pragma warning disable CS1998 // Make local function 'static' 316 | async ReusableTask Test () 317 | { 318 | } 319 | #pragma warning restore CS1998 // Make local function 'static' 320 | 321 | var t1 = Test (); 322 | var t2 = Test (); 323 | var t3 = Test (); 324 | 325 | await t1; 326 | Assert.AreEqual (0, ReusableTaskMethodBuilder.CacheCount, "#1"); 327 | await t2; 328 | Assert.AreEqual (0, ReusableTaskMethodBuilder.CacheCount, "#2"); 329 | await t3; 330 | Assert.AreEqual (0, ReusableTaskMethodBuilder.CacheCount, "#3"); 331 | 332 | } 333 | 334 | [Test] 335 | public async Task Synchronous_ThreeSequential () 336 | { 337 | #pragma warning disable CS1998 // Make local function 'static' 338 | async ReusableTask Test () 339 | { 340 | } 341 | #pragma warning restore CS1998 // Make local function 'static' 342 | 343 | await Test (); 344 | await Test (); 345 | await Test (); 346 | Assert.AreEqual (0, ReusableTaskMethodBuilder.CacheCount, "#1"); 347 | } 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /src/ReusableTasks.Tests/ReusableTask_TTests.cs: -------------------------------------------------------------------------------- 1 | // 2 | // ReusableTask_TTests.cs 3 | // 4 | // Authors: 5 | // Alan McGovern alan.mcgovern@gmail.com 6 | // 7 | // Copyright (C) 2019 Alan McGovern 8 | // 9 | // Permission is hereby granted, free of charge, to any person obtaining 10 | // a copy of this software and associated documentation files (the 11 | // "Software"), to deal in the Software without restriction, including 12 | // without limitation the rights to use, copy, modify, merge, publish, 13 | // distribute, sublicense, and/or sell copies of the Software, and to 14 | // permit persons to whom the Software is furnished to do so, subject to 15 | // the following conditions: 16 | // 17 | // The above copyright notice and this permission notice shall be 18 | // included in all copies or substantial portions of the Software. 19 | // 20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 22 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 24 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 25 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 26 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 27 | // 28 | 29 | 30 | #pragma warning disable IDE0062 // Make local function 'static' 31 | 32 | using System; 33 | using System.Runtime.CompilerServices; 34 | using System.Threading; 35 | using System.Threading.Tasks; 36 | 37 | using NUnit.Framework; 38 | 39 | namespace ReusableTasks.Tests 40 | { 41 | [TestFixture] 42 | public class ReusableTask_TTests 43 | { 44 | SynchronizationContext OriginalContext; 45 | 46 | [SetUp] 47 | public void Setup () 48 | { 49 | TestSynchronizationContext.Instance.ResetCounts (); 50 | OriginalContext = SynchronizationContext.Current; 51 | ReusableTaskMethodBuilder.ClearCache (); 52 | } 53 | 54 | [TearDown] 55 | public void Teardown () 56 | { 57 | SynchronizationContext.SetSynchronizationContext (OriginalContext); 58 | } 59 | 60 | [Test] 61 | public async Task AsTask () 62 | { 63 | async ReusableTask Test () 64 | { 65 | await Task.Delay (1); 66 | return 5; 67 | } 68 | 69 | var task = Test ().AsTask (); 70 | Assert.AreEqual (5, await task, "#1"); 71 | Assert.AreEqual (5, await task, "#2"); 72 | Assert.AreEqual (5, await task, "#3"); 73 | 74 | Assert.AreEqual (1, ReusableTaskMethodBuilder.CacheCount, "#4"); 75 | } 76 | 77 | [Test] 78 | public void Asynchronous_AwaiterGetResultTwice () 79 | { 80 | async ReusableTask Test () 81 | { 82 | await Task.Yield (); 83 | return 1; 84 | } 85 | 86 | var task = Test (); 87 | var awaiter = task.GetAwaiter (); 88 | awaiter.GetResult (); 89 | Assert.Throws (() => awaiter.GetResult (), "#1"); 90 | } 91 | 92 | [Test] 93 | public void Asynchronous_AwaiterOnCompletedTwice () 94 | { 95 | async ReusableTask Test () 96 | { 97 | await Task.Yield (); 98 | return 1; 99 | } 100 | 101 | var task = Test (); 102 | var awaiter = task.GetAwaiter (); 103 | awaiter.OnCompleted (() => { }); 104 | Assert.Throws (() => awaiter.OnCompleted (() => { }), "#1"); 105 | } 106 | 107 | [Test] 108 | public async Task Asynchronous_ConfigureAwaitFalse () 109 | { 110 | var context = TestSynchronizationContext.Instance; 111 | SynchronizationContext.SetSynchronizationContext (context); 112 | 113 | async ReusableTask Test () 114 | { 115 | await Task.Delay (1).ConfigureAwait (false); 116 | await Task.Delay (1).ConfigureAwait (false); 117 | return 1; 118 | } 119 | 120 | await Test ().ConfigureAwait (false); 121 | Assert.AreEqual (0, context.Posted + context.Sent, "#1"); 122 | } 123 | 124 | [Test] 125 | public async Task Asynchronous_ConfigureAwaitTrue () 126 | { 127 | var context = TestSynchronizationContext.Instance; 128 | SynchronizationContext.SetSynchronizationContext (context); 129 | 130 | async ReusableTask Test () 131 | { 132 | await Task.Delay (1).ConfigureAwait (true); 133 | await Task.Delay (1).ConfigureAwait (true); 134 | return 1; 135 | } 136 | 137 | await Test ().ConfigureAwait (true); 138 | Assert.AreEqual (2, context.Posted + context.Sent, "#1"); 139 | } 140 | 141 | [Test] 142 | public void Asynchronous_Exception () 143 | { 144 | async ReusableTask Test () 145 | { 146 | await Task.Yield (); 147 | throw new TimeoutException (); 148 | } 149 | 150 | Assert.ThrowsAsync (async () => await Test ()); 151 | Assert.AreEqual (1, ReusableTaskMethodBuilder.CacheCount, "#1"); 152 | } 153 | 154 | [Test] 155 | public async Task Asynchronous_ValidateFastPath () 156 | { 157 | // Validate the optimisation where ReusableTaskAwaiter fast-paths to ResultHolder 158 | async ReusableTask Test (int count) 159 | { 160 | await Task.Yield (); 161 | if (count > 0) 162 | return await Test (count - 1); 163 | else 164 | return 123; 165 | } 166 | 167 | Assert.AreEqual (123, await Test (5)); 168 | 169 | Assert.AreEqual (6, ReusableTaskMethodBuilder.CacheCount, "#1"); 170 | } 171 | 172 | [Test] 173 | public async Task Asynchronous_IsCompleted () 174 | { 175 | var firstWaiter = new SemaphoreSlim (0, 1); 176 | var secondWaiter = new SemaphoreSlim (0, 1); 177 | 178 | async ReusableTask Test () 179 | { 180 | firstWaiter.Release (); 181 | await secondWaiter.WaitAsync (); 182 | return 1; 183 | } 184 | 185 | var task = Test (); 186 | await firstWaiter.WaitAsync (); 187 | Assert.IsFalse (task.IsCompleted, "#1"); 188 | 189 | secondWaiter.Release (); 190 | int i = 0; 191 | while (!task.IsCompleted && i++ < 1000) 192 | await Task.Yield (); 193 | 194 | Assert.IsTrue (i < 1000, "#2"); 195 | Assert.IsTrue (task.IsCompleted, "#3"); 196 | } 197 | 198 | [Test] 199 | public async Task Asynchronous_IsCompleted_ThenAwait () 200 | { 201 | async ReusableTask Test () 202 | { 203 | await Task.Yield (); 204 | return 1; 205 | } 206 | 207 | var task = Test (); 208 | await task; 209 | Assert.IsFalse (task.IsCompleted, "#1"); 210 | } 211 | 212 | [Test] 213 | public async Task Asynchronous_ThreeConcurrent () 214 | { 215 | async ReusableTask Test () 216 | { 217 | await Task.Yield (); 218 | return 1; 219 | } 220 | 221 | var t1 = Test (); 222 | var t2 = Test (); 223 | var t3 = Test (); 224 | 225 | await t1; 226 | Assert.AreEqual (1, ReusableTaskMethodBuilder.CacheCount, "#1"); 227 | await t2; 228 | Assert.AreEqual (2, ReusableTaskMethodBuilder.CacheCount, "#2"); 229 | await t3; 230 | Assert.AreEqual (3, ReusableTaskMethodBuilder.CacheCount, "#3"); 231 | } 232 | 233 | [Test] 234 | public async Task Asynchronous_ThreeSequential () 235 | { 236 | async ReusableTask Test () 237 | { 238 | await Task.Yield (); 239 | return 1; 240 | } 241 | 242 | await Test (); 243 | await Test (); 244 | await Test (); 245 | 246 | Assert.AreEqual (1, ReusableTaskMethodBuilder.CacheCount, "#1"); 247 | } 248 | 249 | [Test] 250 | public async Task CompletedTask () 251 | { 252 | Assert.AreEqual (1, await ReusableTask.FromResult (1)); 253 | Assert.AreEqual (1, await ReusableTask.FromResult (1).ConfigureAwait (false)); 254 | Assert.AreEqual (1, await ReusableTask.FromResult (1).ConfigureAwait (true)); 255 | Assert.IsTrue (ReusableTask.FromResult (1).IsCompleted); 256 | Assert.IsTrue (ReusableTask.FromResult (1).GetAwaiter ().IsCompleted); 257 | } 258 | 259 | [Test] 260 | public async Task Synchronous_ConfigureAwaitFalse () 261 | { 262 | var context = TestSynchronizationContext.Instance; 263 | SynchronizationContext.SetSynchronizationContext (context); 264 | 265 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously 266 | async ReusableTask Test () 267 | { 268 | return 1; 269 | } 270 | #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously 271 | 272 | await Test ().ConfigureAwait (false); 273 | Assert.AreEqual (0, context.Posted + context.Sent, "#1"); 274 | Assert.AreEqual (0, ReusableTaskMethodBuilder.CacheCount, "#2"); 275 | } 276 | 277 | [Test] 278 | public async Task Synchronous_ConfigureAwaitTrue () 279 | { 280 | var context = TestSynchronizationContext.Instance; 281 | SynchronizationContext.SetSynchronizationContext (context); 282 | 283 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously 284 | async ReusableTask Test () 285 | { 286 | return 1; 287 | } 288 | #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously 289 | 290 | await Test ().ConfigureAwait (true); 291 | Assert.AreEqual (0, context.Posted + context.Sent, "#1"); 292 | Assert.AreEqual (0, ReusableTaskMethodBuilder.CacheCount, "#2"); 293 | } 294 | 295 | [Test] 296 | public void Synchronous_Exception () 297 | { 298 | #pragma warning disable CS1998 // Make local function 'static' 299 | async ReusableTask Test () 300 | { 301 | throw new TimeoutException (); 302 | } 303 | #pragma warning restore CS1998 // Make local function 'static' 304 | 305 | Assert.ThrowsAsync (async () => await Test ()); 306 | Assert.AreEqual (1, ReusableTaskMethodBuilder.CacheCount, "#1"); 307 | } 308 | 309 | [Test] 310 | public void Synchronous_IsCompleted () 311 | { 312 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously 313 | async ReusableTask Test () 314 | { 315 | return 1; 316 | } 317 | #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously 318 | 319 | var task = Test (); 320 | Assert.IsTrue (task.IsCompleted, "#1"); 321 | } 322 | 323 | [Test] 324 | public async Task Synchronous_IsCompleted_ThenAwait () 325 | { 326 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously 327 | async ReusableTask Test () 328 | { 329 | return 1; 330 | } 331 | #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously 332 | 333 | var task = Test (); 334 | await task; 335 | Assert.IsTrue (task.IsCompleted, "#1"); 336 | } 337 | 338 | [Test] 339 | public async Task Synchronous_ThreeConcurrent () 340 | { 341 | #pragma warning disable CS1998 // Make local function 'static' 342 | async ReusableTask Test () 343 | { 344 | return 1; 345 | } 346 | #pragma warning restore CS1998 // Make local function 'static' 347 | 348 | var t1 = Test (); 349 | var t2 = Test (); 350 | var t3 = Test (); 351 | 352 | await t1; 353 | Assert.AreEqual (0, ReusableTaskMethodBuilder.CacheCount, "#1"); 354 | await t2; 355 | Assert.AreEqual (0, ReusableTaskMethodBuilder.CacheCount, "#2"); 356 | await t3; 357 | Assert.AreEqual (0, ReusableTaskMethodBuilder.CacheCount, "#3"); 358 | } 359 | 360 | [Test] 361 | public async Task Synchronous_ThreeSequential () 362 | { 363 | #pragma warning disable CS1998 // Make local function 'static' 364 | async ReusableTask Test () 365 | { 366 | return 1; 367 | } 368 | #pragma warning restore CS1998 // Make local function 'static' 369 | 370 | await Test (); 371 | await Test (); 372 | await Test (); 373 | Assert.AreEqual (0, ReusableTaskMethodBuilder.CacheCount, "#1"); 374 | } 375 | } 376 | } 377 | --------------------------------------------------------------------------------