├── 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 | [](https://badge.fury.io/nu/reusabletasks)
4 | [](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 |
--------------------------------------------------------------------------------