├── .gitignore
├── Directory.Build.props
├── Directory.Build.targets
├── LICENSE.TXT
├── PooledAwait.sln
├── PooledAwait.snk
├── docs
├── _config.yml
└── index.md
├── global.json
├── nuget.config
├── readme.md
├── src
└── PooledAwait
│ ├── AwaitableExtensions.cs
│ ├── ConfiguredYieldAwaitable.cs
│ ├── FireAndForget.cs
│ ├── IResettable.cs
│ ├── Internal
│ ├── BrowsableAttribute.cs
│ ├── Counters.cs
│ ├── LazyTaskStateT.cs
│ ├── ManualResetValueTaskSourceCore.cs
│ ├── Nothing.cs
│ ├── PooledStateT.cs
│ ├── StateMachineBox.cs
│ ├── TaskUtils.cs
│ └── ThrowHelper.cs
│ ├── LazyTaskCompletionSource.cs
│ ├── LazyTaskCompletionSourceT.cs
│ ├── MethodBuilders
│ ├── FireAndForgetMethodBuilder.cs
│ ├── PooledTaskMethodBuilder.cs
│ ├── PooledTaskMethodBuilderT.cs
│ ├── PooledValueTaskMethodBuilder.cs
│ └── PooledValueTaskMethodBuilderT.cs
│ ├── Pool.cs
│ ├── PoolSizeAttribute.cs
│ ├── PoolT.cs
│ ├── PooledAwait.csproj
│ ├── PooledTask.cs
│ ├── PooledTaskT.cs
│ ├── PooledValueTask.cs
│ ├── PooledValueTaskSource.cs
│ ├── PooledValueTaskSourceT.cs
│ ├── PooledValueTaskT.cs
│ ├── Properties
│ └── AssemblyInfo.cs
│ └── ValueTaskCompletionSourceT.cs
├── tests
├── Benchmark
│ ├── Benchmark.csproj
│ ├── ComparisonBenchmarks.cs
│ ├── PoolStrategyTests.cs
│ └── Program.cs
└── PooledAwait.Test
│ ├── BasicTests.cs
│ ├── BoxTests.cs
│ ├── CompletedTaskIdentityTests.cs
│ ├── ComplexAsyncTests.cs
│ ├── ContextTests.cs
│ ├── FireAndForgetTests.cs
│ ├── LazyTaskTests.cs
│ ├── NullableTypes.cs
│ ├── PoolTests.cs
│ ├── PooledAwait.Test.csproj
│ ├── PooledValueTaskSourceTests.cs
│ └── ValueTaskCompletionSourceTests.cs
└── version.json
/.gitignore:
--------------------------------------------------------------------------------
1 | .vs/
2 | bin/
3 | obj/
4 | BenchmarkDotNet.Artifacts/
5 | *.csproj.user
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | $(MSBuildThisFileDirectory)PooledAwait.snk
4 | true
5 | false
6 | preview
7 | enable
8 | mgravell
9 | NU5104;NU5125
10 | 2019 Marc Gravell
11 | true
12 |
13 | git
14 | https://github.com/mgravell/PooledAwait/
15 | https://github.com/mgravell/PooledAwait/
16 | MIT
17 |
18 | true
19 | embedded
20 | en-US
21 | false
22 | true
23 |
24 |
25 | true
26 | true
27 | true
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)'))
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/LICENSE.TXT:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Marc Gravell
4 |
5 | All rights reserved.
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.
24 |
25 | Additional licenses:
26 |
27 | ManualResetValueTaskSourceCore is Copyright (c) .NET Foundation and Contributors,
28 | and is made available under The MIT License
--------------------------------------------------------------------------------
/PooledAwait.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29006.145
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{6DC27E35-76A1-4DB4-9A43-BEA952B688E8}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PooledAwait", "src\PooledAwait\PooledAwait.csproj", "{140C1306-3443-4F45-B132-27197EB4D146}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{04573B33-34F0-429A-AC95-376A9D819666}"
11 | ProjectSection(SolutionItems) = preProject
12 | .gitignore = .gitignore
13 | Directory.Build.props = Directory.Build.props
14 | Directory.Build.targets = Directory.Build.targets
15 | global.json = global.json
16 | LICENSE.TXT = LICENSE.TXT
17 | nuget.config = nuget.config
18 | readme.md = readme.md
19 | version.json = version.json
20 | EndProjectSection
21 | EndProject
22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{AF54CE9C-B78F-4023-8A66-367509185F67}"
23 | EndProject
24 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmark", "tests\Benchmark\Benchmark.csproj", "{CC23CF2D-D42E-4470-8BBD-A847DB7CB8CC}"
25 | EndProject
26 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PooledAwait.Test", "tests\PooledAwait.Test\PooledAwait.Test.csproj", "{1AD2D514-48A4-4631-867E-F4D5D2765C7B}"
27 | EndProject
28 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{9E246D47-3A08-4D10-A66F-DFEEC21BFF34}"
29 | ProjectSection(SolutionItems) = preProject
30 | docs\index.md = docs\index.md
31 | EndProjectSection
32 | EndProject
33 | Global
34 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
35 | Debug|Any CPU = Debug|Any CPU
36 | Release|Any CPU = Release|Any CPU
37 | EndGlobalSection
38 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
39 | {140C1306-3443-4F45-B132-27197EB4D146}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
40 | {140C1306-3443-4F45-B132-27197EB4D146}.Debug|Any CPU.Build.0 = Debug|Any CPU
41 | {140C1306-3443-4F45-B132-27197EB4D146}.Release|Any CPU.ActiveCfg = Release|Any CPU
42 | {140C1306-3443-4F45-B132-27197EB4D146}.Release|Any CPU.Build.0 = Release|Any CPU
43 | {CC23CF2D-D42E-4470-8BBD-A847DB7CB8CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
44 | {CC23CF2D-D42E-4470-8BBD-A847DB7CB8CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
45 | {CC23CF2D-D42E-4470-8BBD-A847DB7CB8CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
46 | {CC23CF2D-D42E-4470-8BBD-A847DB7CB8CC}.Release|Any CPU.Build.0 = Release|Any CPU
47 | {1AD2D514-48A4-4631-867E-F4D5D2765C7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
48 | {1AD2D514-48A4-4631-867E-F4D5D2765C7B}.Debug|Any CPU.Build.0 = Debug|Any CPU
49 | {1AD2D514-48A4-4631-867E-F4D5D2765C7B}.Release|Any CPU.ActiveCfg = Release|Any CPU
50 | {1AD2D514-48A4-4631-867E-F4D5D2765C7B}.Release|Any CPU.Build.0 = Release|Any CPU
51 | EndGlobalSection
52 | GlobalSection(SolutionProperties) = preSolution
53 | HideSolutionNode = FALSE
54 | EndGlobalSection
55 | GlobalSection(NestedProjects) = preSolution
56 | {140C1306-3443-4F45-B132-27197EB4D146} = {6DC27E35-76A1-4DB4-9A43-BEA952B688E8}
57 | {CC23CF2D-D42E-4470-8BBD-A847DB7CB8CC} = {AF54CE9C-B78F-4023-8A66-367509185F67}
58 | {1AD2D514-48A4-4631-867E-F4D5D2765C7B} = {AF54CE9C-B78F-4023-8A66-367509185F67}
59 | EndGlobalSection
60 | GlobalSection(ExtensibilityGlobals) = postSolution
61 | SolutionGuid = {3DCCF6DC-DEB4-48FB-BD2E-2FBC48C054EC}
62 | EndGlobalSection
63 | EndGlobal
64 |
--------------------------------------------------------------------------------
/PooledAwait.snk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mgravell/PooledAwait/e1b18312be845e77751178be1793aaee95ab22c6/PooledAwait.snk
--------------------------------------------------------------------------------
/docs/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-cayman
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | # PooledAwait
2 |
3 | Low-allocation utilies for writing `async` methods, and related tools
4 |
5 | ### Contents
6 |
7 | - [`PooledValueTask` / `PooledValueTask`](#pooledvaluetask--pooledvaluetaskt)
8 | - [`PooledTask` / `PooledTask`](#pooledtask--pooledtaskt)
9 | - [`FireAndForget`](#fireandforget)
10 | - [`ConfiguredYieldAwaitable`](#configuredyieldawaitable)
11 | - [`ValueTaskCompletionSource`](#valuetaskcompletionsourcet)
12 | - [`PooledValueTaskSource / PooledValueTaskSource`](#pooledvaluetasksource--pooledvaluetasksourcet)
13 | - [`LazyTaskCompletionSource / LazyTaskCompletionSource`](#lazytaskcompletionsource--lazytaskcompletionsourcet)
14 | - [`Pool`](#pool)
15 |
16 | ---
17 |
18 | ## `PooledValueTask` / `PooledValueTask`
19 |
20 | These are the main tools of the library; their purpose is to remove the boxing of the async state-machine and builder that happens when a method
21 | marked `async` performs an `await` on an awaitable target that *is not yet complete*, i.e.
22 |
23 | ``` c#
24 | async ValueTask SomeMethod()
25 | {
26 | await Task.Yield(); // *is not yet complete*
27 | return 42
28 | }
29 | ```
30 |
31 | If you've ever looked at an application that uses `async` / `await` in a memory profiler and seen things like `System.Runtime.CompilerServices.AsyncTaskMethodBuilder``1.AsyncStateMachineBox``1`
32 | or `YourLib.<g__Inner|8_0>d`, then that's what I'm talking about. You can avoid this by simply using a different return type:
33 |
34 | - `PooledValueTask` instead of `ValueTask`
35 | - `PooledValueTask` instead of `ValueTask`
36 |
37 | For `private` / `internal` methods, you can probably just *change the return type directly*:
38 |
39 | ``` c#
40 | private async PooledValueTask SomeMethod()
41 | {
42 | await Task.Yield(); // *is not yet complete*
43 | return 42
44 | }
45 | ```
46 |
47 | For methods on your `public` API surface, you can use a "local function" to achieve the same thing without changing the exposed return type:
48 |
49 | ``` c#
50 | public ValueTask SomeMethod() // not marked async
51 | {
52 | return Impl();
53 | async PooledValueTask() Impl()
54 | {
55 | await Task.Yield(); // *is not yet complete*
56 | return 42
57 | }
58 | }
59 | ```
60 |
61 | (all of the `Pooled*` types have `implicit` conversion operators to their more well-recognized brethren).
62 |
63 | And that's it! That's all you have to do. The "catch" (there's always a catch) is that awaiting the same pending operation *more than once* **no longer works**:
64 |
65 | ``` c#
66 | var pending = SomeIncompleteMethodAsync(); // note no "await" here
67 |
68 | var x = await pending;
69 | var y = await pending; // BOOM! await the **same result**
70 | ```
71 |
72 | In reality, **this almost never happens**. Usually you `await` something *once*, *almost always* right away. So... yeah.
73 |
74 | ---
75 |
76 | ## `PooledTask` / `PooledTask`
77 |
78 | These work very similarly to `PooledValueTask[]`, but for the `Task[]` API. It can't be *quite* as frugal, as in most cases a `Task[]`
79 | will still need to be allocated (unless it is the non-generic `PooledTask` signature, and the operation completes synchronously), but it
80 | still avoids the state-machine box etc. Note that this API **is not** impacted by the "you can only await it once" change (you can
81 | await these as many times as you like - they are, after all, `Task[]`), but again: *this is used incredibly rarely anyway*.
82 |
83 | ## `FireAndForget`
84 |
85 | Ever find yourself needing a fire-and-forget API? This adds one. All you do is declare the return type as `FireAndForget`:
86 |
87 | ``` c#
88 | FireAndForget SomeMethod(...) {
89 | // .. things before the first incomplete await happen on the calling thread
90 | await SomeIncompleteMethod();
91 | // .. other bits continue running in the background
92 | }
93 | ```
94 |
95 | As soon as the method uses `await` against an incomplete operation, the calling
96 | task regains control as though it were complete; the rest of the operation continues in the background. The caller can simply `await`
97 | the fire-and-forget method with confidence that it only runs synchronously to the first incomplete operation. If you're not in an `async`
98 | method, you can use "discard" to tell the compiler not to tell you to `await` it:
99 |
100 | ``` c#
101 | _ = SomeFireAndForgetMethodAsync();
102 | ```
103 |
104 | You won't get unobserved-task-exception problems. If you want to see any exceptions that happen, there is an event `FireAndForget.Exception`
105 | that you can subscribe to. Otherwise, they just evaporate.
106 |
107 | ---
108 |
109 | ## `ConfiguredYieldAwaitable`
110 |
111 | Related to `FireAndForget` - when you `await Task.Yield()` it always respects the sync-context/task-scheduler; sometimes *you don't want to*.
112 | For many awaitables there is a `.ConfigureAwait(continueOnCapturedContext: false)` method that you can use to suppress this, but
113 | not on `Task.Yield()`... *until now*. Usage is, as you would expect:
114 |
115 | ``` c#
116 | await Task.Yield().ConfigureAwait(false);
117 | ```
118 |
119 | ---
120 |
121 | ## `ValueTaskCompletionSource`
122 |
123 | Do you make use of `TaskCompletionSource`? Do you hate that this adds another allocation *on top of* the `Task` that you actually wanted?
124 | `ValueTaskCompletionSource` is your friend. It uses smoke and magic to work like `TaskCompletionSource`, but without the extra
125 | allocation (unless it discovers that the magic isn't working for your system). Usage:
126 |
127 | ``` c#
128 | var source = ValueTaskCompletionSource.Create();
129 | // ...
130 | source.TrySetResult(42); // etc
131 | ```
132 |
133 | The main difference here is that you now have a `struct` instead of a `class`. If you want to test whether an instance is a *real* value
134 | (as opposed to the `default`), check `.HasTask`.
135 |
136 | ---
137 |
138 | ## `PooledValueTaskSource` / `PooledValueTaskSource`
139 |
140 | These again work like `TaskCompletionSource`, but a: for `ValueType[]`, and b: with the same zero-allocation features that
141 | `PooledValueTask` / `PooledValueTask` exhibit. Once again, the "catch" is that you can only await their `.Task` *once*. Usage:
142 |
143 | ``` c#
144 | var source = PooledValueTaskSource.Create();
145 | // ...
146 | source.TrySetResult(42); // etc
147 | ```
148 |
149 | ---
150 |
151 | ## `LazyTaskCompletionSource / LazyTaskCompletionSource`
152 |
153 | Sometimes, you have an API where you *aren't sure* whether someone is subscribing to the `Task`/`Task` results - for example
154 | you have properties like:
155 |
156 | ``` c#
157 | public Task SomeStepCompleted { get; }
158 | ```
159 |
160 | It would be a shame to allocate a `Task` for this *just in case*, so `LazyTaskCompletionSource[]` allows you to *rent* state
161 | that can manage *lazily* creating a task. If the `.Task` is read before the value is set, a *source* is used to provide a
162 | pending task; if the result gets set before the value is read, then some optimizations may be possible (`Task.CompletedTask`, etc).
163 | And if the `.Task` is never queried: no task or source is allocated. These types are disposable; disposing them releases any
164 | rented state for re-use.
165 |
166 | ---
167 |
168 | ## `Pool`
169 |
170 | Ever need a light-weight basic pool of objects? That's this. Nothing fancy. The first API is a simple get/put:
171 |
172 | ``` c#
173 | var obj = Pool.TryRent() ?? new SomeType();
174 | // ...
175 | Pool.Return(obj);
176 | ```
177 |
178 | Note that it leaves creation to you (hence the `?? new SomeType()`), and it is the caller's responsibility to not retain and access
179 | a reference object that you have notionally returned to the pool.
180 |
181 | Considerations:
182 |
183 | - you may wish to use `try`/`finally` to put things back into the pool even if you leave through failure
184 | - if the object might **unnecessarily** keep large graphs of sub-objects "reachable" (in terms of GC), you should ensure that any references are wiped before putting an object into the pool
185 | - if the object implements `IResettable`, the pool will automatically call the `Reset()` method for you before storing items in the pool
186 |
187 | A second API is exposed for use with value-types; there are a lot of scenarios in which you have some state that you need to expose
188 | to an API that takes `object` - especially with callbacks like `WaitCallback`, `SendOrPostCallback`, `Action`, etc. The data
189 | will only be unboxed once at the receiver - so: rather than use a *regular* box, we can *rent* a box. Also, if you have multiple items of
190 | state that you need to convey - consider a value-tuple.
191 |
192 | ``` c#
193 | int id = ...
194 | string name = ...
195 | var obj = Pool.Box((id, name));
196 | // ... probably pass obj to a callback-API
197 | ```
198 |
199 | then later:
200 |
201 | ``` c#
202 | (var id, var name) = Pool.UnboxAndReturn<(int, string)>(obj);
203 | // use id/name as usual
204 | ```
205 |
206 | It is the caller's responsibility to only access the state once.
207 |
208 | The pool is global (`static`) and pretty modest in size. You can control it *a bit* by adding `[PoolSize(...)]` to the custom
209 | classes and value-types that you use.
210 |
211 |
212 |
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "3.0.100",
4 | "rollForward": "latestMajor",
5 | "allowPrerelease": false
6 | }
7 | }
--------------------------------------------------------------------------------
/nuget.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | [Documentation](https://mgravell.github.io/PooledAwait/)
2 |
3 | # What is this?
4 |
5 | You know how `async` methods that `await` something **incomplete** end up creating a few objects, right? There's
6 | the boxed state machine, an `Action` that moves it forward, a `Task[]`, etc - right?
7 |
8 | Well... what about if there just **wasn't**?
9 |
10 | And what if all you had to do was change your `async ValueTask` method to `async PooledValueTask`?
11 |
12 | And I hear you; you're saying "but I can't change the public API!". But what if a `PooledValueTask` really *was*
13 | a `ValueTask`? So you can just cheat:
14 |
15 | ``` c#
16 | public ValueTask DoTheThing() // the outer method is not async
17 | {
18 | return ReallyDoTheThing(this);
19 | static async PooledValueTask ReallyDoTheThing(SomeType obj)
20 | {
21 | ... await ...
22 | // (use obj.* instead of this.*)
23 | ... return ...
24 | }
25 | }
26 | ```
27 |
28 | (the use of a `static` local function here avoids a `<>c__DisplayClass` wrapper from how the local-function capture context is implemented by the compiler)
29 |
30 | And how about if maybe just maybe in the future it could be ([if this happens](https://github.com/dotnet/csharplang/issues/1407)) just:
31 |
32 | ``` c#
33 | [SomeKindOfAttribute] // <=== this is the only change
34 | public async ValueTask DoTheThing()
35 | {
36 | // no changes here at all
37 | }
38 | ```
39 |
40 | (although note that in some cases it can work *better* with the `static` trick, as above)
41 |
42 | Would that be awesome? Because that's what this is!
43 |
44 | # How does that work?
45 |
46 | The `PooledValueTask[]` etc exist mostly to define a custom **builder**. The builder in this library uses aggressive pooling of classes
47 | that replace the *boxed* approach used by default; we recycle them when the state machine completes.
48 |
49 | It also makes use of the `IValueTaskSource[]` API to allow incomplete operations to be represented without a `Task[]`, but with a custom backer.
50 | And we pool that too, recycling it when the task is *awaited*. The only downside: you can't `await` the same result *twice* now, because
51 | once you've awaited it the first time, **it has gone**. A cycling token is used to make sure you can't accidentally read the incorrect
52 | values after the result has been awaited.
53 |
54 | We can *even* do this for `Task[]`, except here we can only avoid the boxed state machine; hence `PooledTask[]` exists too. No custom backing in this case, though, since a `Task[]` will
55 | need to be allocated (except for `Task.CompletedTask`, which we special-case).
56 |
57 | # Test results
58 |
59 | Based on an operation that uses `Task.Yield()` to ensure that the operations are incomplete; ".NET" means the inbuilt out-of-the box implementation; "Pooled" means the implementation from this library.
60 |
61 | In particular, notice:
62 |
63 | - zero allocations for `PooledValueTask[]` vs `ValueTask[]` (on .NET Core; *significantly reduced* on .NET Framework)
64 | - *reduced* allocations for `PooledTask[]` vs `Task[]`
65 | - no performance degredation; just lower allocations
66 |
67 | ``` txt
68 | | Method | Job | Runtime | Categories | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
69 | |------- |----- |-------- |------------- |---------:|----------:|----------:|-------:|-------:|-------:|----------:|
70 | | .NET | Clr | Clr | Task | 2.159 us | 0.0427 us | 0.0474 us | 0.0508 | 0.0039 | - | 344 B |
71 | | Pooled | Clr | Clr | Task | 2.037 us | 0.0246 us | 0.0230 us | 0.0273 | 0.0039 | - | 182 B |
72 | | .NET | Core | Core | Task | 1.397 us | 0.0024 us | 0.0022 us | 0.0176 | - | - | 120 B |
73 | | Pooled | Core | Core | Task | 1.349 us | 0.0058 us | 0.0054 us | 0.0098 | - | - | 72 B |
74 | | | | | | | | | | | | |
75 | | .NET | Clr | Clr | Task | 2.065 us | 0.0200 us | 0.0167 us | 0.0508 | 0.0039 | - | 336 B |
76 | | Pooled | Clr | Clr | Task | 1.979 us | 0.0179 us | 0.0167 us | 0.0273 | 0.0039 | - | 182 B |
77 | | .NET | Core | Core | Task | 1.390 us | 0.0159 us | 0.0149 us | 0.0176 | - | - | 112 B |
78 | | Pooled | Core | Core | Task | 1.361 us | 0.0055 us | 0.0051 us | 0.0098 | - | - | 72 B |
79 | | | | | | | | | | | | |
80 | | .NET | Clr | Clr | ValueTask | 2.087 us | 0.0403 us | 0.0431 us | 0.0547 | 0.0078 | 0.0039 | 352 B |
81 | | Pooled | Clr | Clr | ValueTask | 1.924 us | 0.0248 us | 0.0220 us | 0.0137 | 0.0020 | - | 100 B |
82 | | .NET | Core | Core | ValueTask | 1.405 us | 0.0078 us | 0.0073 us | 0.0195 | - | - | 128 B |
83 | | Pooled | Core | Core | ValueTask | 1.374 us | 0.0116 us | 0.0109 us | - | - | - | - |
84 | | | | | | | | | | | | |
85 | | .NET | Clr | Clr | ValueTask | 2.056 us | 0.0206 us | 0.0183 us | 0.0508 | 0.0039 | - | 344 B |
86 | | Pooled | Clr | Clr | ValueTask | 1.948 us | 0.0388 us | 0.0416 us | 0.0137 | 0.0020 | - | 100 B |
87 | | .NET | Core | Core | ValueTask | 1.408 us | 0.0140 us | 0.0117 us | 0.0176 | - | - | 120 B |
88 | | Pooled | Core | Core | ValueTask | 1.366 us | 0.0039 us | 0.0034 us | - | - | - | - |
89 | ```
90 |
91 | Note that *most* of the remaining allocations are actually the work-queue internals of `Task.Yield()` (i.e. how
92 | `ThreadPool.QueueUserWorkItem` works) - we've removed virtually all of the unnecessary overheads that came from the
93 | `async` machinery. Most real-world scenarios aren't using `Task.Yield()` - they are waiting on external data, etc - so
94 | they won't see these. Plus they are effectively zero on .NET Core 3.
95 |
96 | The tests do the exact same thing; the only thing that changes is the return type, i.e. whether it is
97 | `async Task`, `async ValueTask`, `async PooledTask` or `async PooledValueTask`.
98 | All of them have the same threading/execution-context/sync-context semantics; there's no cheating going on.
99 |
100 |
--------------------------------------------------------------------------------
/src/PooledAwait/AwaitableExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.CompilerServices;
2 |
3 | namespace PooledAwait
4 | {
5 | ///
6 | /// Provides async/await-related extension methods
7 | ///
8 | public static class AwaitableExtensions
9 | {
10 | /// Controls whether a yield operation should respect captured context
11 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
12 | public static ConfiguredYieldAwaitable ConfigureAwait(this YieldAwaitable _, bool continueOnCapturedContext)
13 | => new ConfiguredYieldAwaitable(continueOnCapturedContext);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/PooledAwait/ConfiguredYieldAwaitable.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 | using System.Threading;
4 |
5 | namespace PooledAwait
6 | {
7 | /// Provides an awaitable context for switching into a target environment.
8 | public readonly struct ConfiguredYieldAwaitable
9 | {
10 | ///
11 | public override bool Equals(object? obj) => obj is ConfiguredYieldAwaitable other && other._continueOnCapturedContext == _continueOnCapturedContext;
12 | ///
13 | public override int GetHashCode() => _continueOnCapturedContext ? 1 : 0;
14 | ///
15 | public override string ToString() => nameof(ConfiguredYieldAwaitable);
16 |
17 | private readonly bool _continueOnCapturedContext;
18 |
19 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
20 | internal ConfiguredYieldAwaitable(bool continueOnCapturedContext)
21 | => _continueOnCapturedContext = continueOnCapturedContext;
22 |
23 | /// Gets an awaiter for this .
24 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
25 | public ConfiguredYieldAwaiter GetAwaiter()
26 | => new ConfiguredYieldAwaiter(_continueOnCapturedContext);
27 |
28 | /// Provides an awaitable context for switching into a target environment.
29 | public readonly struct ConfiguredYieldAwaiter : ICriticalNotifyCompletion, INotifyCompletion
30 | {
31 | ///
32 | public override bool Equals(object? obj) => obj is ConfiguredYieldAwaiter other && other._continueOnCapturedContext == _continueOnCapturedContext;
33 | ///
34 | public override int GetHashCode() => _continueOnCapturedContext ? 1 : 0;
35 | ///
36 | public override string ToString() => nameof(ConfiguredYieldAwaiter);
37 |
38 | private readonly bool _continueOnCapturedContext;
39 |
40 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
41 | internal ConfiguredYieldAwaiter(bool continueOnCapturedContext)
42 | => _continueOnCapturedContext = continueOnCapturedContext;
43 |
44 | /// Gets whether a yield is not required.
45 | public bool IsCompleted
46 | {
47 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
48 | get => false;
49 | }
50 |
51 | /// Ends the await operation.
52 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
53 | public void GetResult() { }
54 |
55 | /// Posts the back to the current context.
56 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
57 | public void OnCompleted(Action continuation)
58 | {
59 | if (_continueOnCapturedContext) YieldFlowContext(continuation, true);
60 | else YieldNoContext(continuation, true);
61 | }
62 |
63 | /// Posts the back to the current context.
64 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
65 | public void UnsafeOnCompleted(Action continuation)
66 | {
67 | if (_continueOnCapturedContext) YieldFlowContext(continuation, false);
68 | else YieldNoContext(continuation, false);
69 | }
70 |
71 | private static readonly WaitCallback s_waitCallbackRunAction = state => ((Action?)state)?.Invoke();
72 |
73 | #if PLAT_THREADPOOLWORKITEM
74 | private sealed class ContinuationWorkItem : IThreadPoolWorkItem
75 | {
76 | private Action? _continuation;
77 |
78 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
79 | private ContinuationWorkItem() => Internal.Counters.ItemBoxAllocated.Increment();
80 |
81 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
82 | public static ContinuationWorkItem Create(Action continuation)
83 | {
84 | var box = Pool.TryGet() ?? new ContinuationWorkItem();
85 | box._continuation = continuation;
86 | return box;
87 | }
88 |
89 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
90 | void IThreadPoolWorkItem.Execute()
91 | {
92 | var callback = _continuation;
93 | _continuation = null;
94 | Pool.TryPut(this);
95 | callback?.Invoke();
96 | }
97 | }
98 | #endif
99 | [MethodImpl(MethodImplOptions.NoInlining)] // no-one ever calls ConfigureAwait(true)!
100 | private static void YieldFlowContext(Action continuation, bool flowContext)
101 | {
102 | var awaiter = default(YieldAwaitable.YieldAwaiter);
103 | if (flowContext) awaiter.OnCompleted(continuation);
104 | else awaiter.UnsafeOnCompleted(continuation);
105 | }
106 |
107 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
108 | private static void YieldNoContext(Action continuation, bool flowContext)
109 | {
110 | if (flowContext)
111 | {
112 | ThreadPool.QueueUserWorkItem(s_waitCallbackRunAction, continuation);
113 | }
114 | else
115 | {
116 | #if PLAT_THREADPOOLWORKITEM
117 | ThreadPool.UnsafeQueueUserWorkItem(ContinuationWorkItem.Create(continuation), false);
118 | #elif NETSTANDARD1_3
119 | ThreadPool.QueueUserWorkItem(s_waitCallbackRunAction, continuation);
120 | #else
121 | ThreadPool.UnsafeQueueUserWorkItem(s_waitCallbackRunAction, continuation);
122 | #endif
123 | }
124 | }
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/PooledAwait/FireAndForget.cs:
--------------------------------------------------------------------------------
1 | using PooledAwait.Internal;
2 | using System;
3 | using System.Runtime.CompilerServices;
4 | using System.Threading.Tasks;
5 |
6 | namespace PooledAwait
7 | {
8 | ///
9 | /// Represents an operation that completes at the first incomplete await,
10 | /// with the remainder continuing in the background
11 | ///
12 | [AsyncMethodBuilder(typeof(MethodBuilders.FireAndForgetMethodBuilder))]
13 | public readonly struct FireAndForget
14 | {
15 | ///
16 | public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException();
17 | ///
18 | public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException();
19 | ///
20 | public override string ToString() => nameof(FireAndForget);
21 |
22 | ///
23 | /// Raised when exceptions occur on fire-and-forget methods
24 | ///
25 | public static event Action? Exception;
26 |
27 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
28 | internal static void OnException(Exception exception)
29 | {
30 | if (exception != null) Exception?.Invoke(exception);
31 | }
32 |
33 | ///
34 | /// Gets the instance as a value-task
35 | ///
36 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
37 | public ValueTask AsValueTask() => default;
38 |
39 | ///
40 | /// Gets the instance as a task
41 | ///
42 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
43 | public Task AsTask() => TaskUtils.CompletedTask;
44 |
45 | ///
46 | /// Gets the instance as a task
47 | ///
48 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
49 | public static implicit operator Task(FireAndForget _) => TaskUtils.CompletedTask;
50 |
51 | ///
52 | /// Gets the instance as a value-task
53 | ///
54 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
55 | public static implicit operator ValueTask(FireAndForget _) => default;
56 |
57 | ///
58 | /// Gets the awaiter for the instance
59 | ///
60 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
61 | public ValueTaskAwaiter GetAwaiter() => default(ValueTask).GetAwaiter();
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/PooledAwait/IResettable.cs:
--------------------------------------------------------------------------------
1 | namespace PooledAwait
2 | {
3 | ///
4 | /// Indicates that an object can be reset
5 | ///
6 | public interface IResettable
7 | {
8 | ///
9 | /// Resets this instance
10 | ///
11 | public void Reset();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/PooledAwait/Internal/BrowsableAttribute.cs:
--------------------------------------------------------------------------------
1 | #if NETSTANDARD1_3
2 | using System;
3 |
4 | namespace PooledAwait.Internal
5 | {
6 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)]
7 | internal sealed class BrowsableAttribute : Attribute
8 | {
9 | public BrowsableAttribute(bool _) { }
10 | }
11 | }
12 | #endif
--------------------------------------------------------------------------------
/src/PooledAwait/Internal/Counters.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Threading;
3 |
4 | namespace PooledAwait.Internal
5 | {
6 | internal static class Counters
7 | {
8 | internal struct Counter
9 | {
10 | private long _value;
11 | [Conditional("DEBUG")]
12 | public void Increment() => Interlocked.Increment(ref _value);
13 | #if !DEBUG
14 | [System.Obsolete("Release only", false)]
15 | #endif
16 | public long Value => Interlocked.Read(ref _value);
17 | #pragma warning disable CS0618
18 | public override string ToString() => Value.ToString();
19 | #pragma warning restore CS0618
20 | public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException();
21 | public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException();
22 | public void Reset() => Interlocked.Exchange(ref _value, 0);
23 | }
24 |
25 | internal static Counter
26 | SetStateMachine,
27 | PooledStateAllocated,
28 | PooledStateRecycled,
29 | StateMachineBoxAllocated,
30 | StateMachineBoxRecycled,
31 | ItemBoxAllocated,
32 | TaskAllocated,
33 | LazyStateAllocated;
34 |
35 | #if !DEBUG
36 | [System.Obsolete("Release only", false)]
37 | #endif
38 | public static long TotalAllocations =>
39 | PooledStateAllocated.Value + StateMachineBoxAllocated.Value
40 | + ItemBoxAllocated.Value + TaskAllocated.Value
41 | + SetStateMachine.Value // SetStateMachine usually means a boxed value
42 | + LazyStateAllocated.Value;
43 |
44 | internal static void Reset()
45 | {
46 | SetStateMachine.Reset();
47 | PooledStateAllocated.Reset();
48 | PooledStateRecycled.Reset();
49 | StateMachineBoxAllocated.Reset();
50 | StateMachineBoxRecycled.Reset();
51 | ItemBoxAllocated.Reset();
52 | TaskAllocated.Reset();
53 | LazyStateAllocated.Reset();
54 | }
55 |
56 | #if !DEBUG
57 | [System.Obsolete("Release only", false)]
58 | #endif
59 | internal static string Summary()
60 | => $@"SetStateMachine: {SetStateMachine.Value}
61 | PooledStateAllocated: {PooledStateAllocated.Value}
62 | PooledStateRecycled: {PooledStateRecycled.Value}
63 | StateMachineBoxAllocated: {StateMachineBoxAllocated.Value}
64 | StateMachineBoxRecycled: {StateMachineBoxRecycled.Value}
65 | ItemBoxAllocated: {ItemBoxAllocated.Value}
66 | TaskAllocated: {TaskAllocated.Value}
67 | LazyStateAllocated: {LazyStateAllocated.Value}";
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/PooledAwait/Internal/LazyTaskStateT.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | namespace PooledAwait.Internal
7 | {
8 | internal sealed class LazyTaskState
9 | {
10 | private short _version;
11 | private T _result;
12 | private Exception? _exception;
13 | private Task? _task;
14 | private bool _isComplete;
15 | private ValueTaskCompletionSource _source;
16 |
17 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
18 | private void CheckTokenInsideLock(short token)
19 | {
20 | if (token != _version) ThrowHelper.ThrowInvalidOperationException();
21 | }
22 |
23 | public Task GetTask(short token)
24 | {
25 | lock (this)
26 | {
27 | CheckTokenInsideLock(token);
28 | if (_task != null) { }
29 | else if (_exception is OperationCanceledException) _task = TaskUtils.TaskFactory.Canceled;
30 | else if (_exception != null) _task = TaskUtils.FromException(_exception);
31 | else if (_isComplete) _task = typeof(T) == typeof(Nothing) ? TaskUtils.CompletedTask : TaskUtils.TaskFactory.FromResult(_result);
32 | else
33 | {
34 | _source = ValueTaskCompletionSource.Create();
35 | _task = _source.Task;
36 | }
37 | return _task;
38 | }
39 | }
40 |
41 | internal bool IsValid(short token) => Volatile.Read(ref _version) == token;
42 | internal bool HasSource
43 | {
44 | get
45 | {
46 | lock (this) { return !_source.IsNull; }
47 | }
48 | }
49 | internal bool HasTask => Volatile.Read(ref _task) != null;
50 |
51 | public bool TrySetResult(short token, T result)
52 | {
53 | lock (this)
54 | {
55 | if (_isComplete) return false;
56 | if (token != _version) return false;
57 | _isComplete = true;
58 | if (!_source.IsNull) return _source.TrySetResult(result);
59 | _result = result;
60 | return true;
61 | }
62 | }
63 |
64 | public bool TrySetException(short token, Exception exception)
65 | {
66 | lock (this)
67 | {
68 | if (_isComplete) return false;
69 | if (token != _version) return false;
70 | _isComplete = true;
71 | if (!_source.IsNull) return _source.TrySetException(exception);
72 | _exception = exception;
73 | return true;
74 | }
75 | }
76 |
77 | public bool TrySetCanceled(short token, CancellationToken cancellationToken = default)
78 | {
79 | lock (this)
80 | {
81 | if (_isComplete) return false;
82 | if (token != _version) return false;
83 | _isComplete = true;
84 | if (!_source.IsNull) return _source.TrySetCanceled(cancellationToken);
85 | _task = TaskUtils.TaskFactory.Canceled;
86 | return true;
87 | }
88 | }
89 |
90 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
91 | public static LazyTaskState Create() => Pool>.TryGet() ?? new LazyTaskState();
92 |
93 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
94 | private LazyTaskState()
95 | {
96 | Counters.LazyStateAllocated.Increment();
97 | _result = default!;
98 | _version = InitialVersion;
99 | }
100 |
101 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
102 | public static LazyTaskState CreateConstant(T value)
103 | {
104 | var obj = new LazyTaskState
105 | {
106 | _version = Constant
107 | };
108 | obj.TrySetResult(Constant, value);
109 | return obj;
110 | }
111 |
112 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
113 | public static LazyTaskState CreateCanceled()
114 | {
115 | var obj = new LazyTaskState
116 | {
117 | _version = Constant
118 | };
119 | obj.TrySetCanceled(Constant);
120 | return obj;
121 | }
122 |
123 | const short InitialVersion = 0, Constant = InitialVersion - 1;
124 |
125 | internal short Version
126 | {
127 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
128 | get => _version;
129 | }
130 |
131 | [MethodImpl(MethodImplOptions.NoInlining)]
132 | internal void Recycle(short token)
133 | {
134 | if (token == Constant) return; // never recycle constant values; this is by design
135 |
136 | if (Volatile.Read(ref _version) != token) return; // wrong version; all bets are off!
137 |
138 | if (!Volatile.Read(ref _isComplete)) // if incomplete, try to cancel
139 | {
140 | if (!TrySetCanceled(token)) return; // if that didn't work... give up - don't recycle
141 | }
142 |
143 | bool haveLock = false;
144 | try
145 | {
146 | // only if uncontested; we're not waiting in a dispose
147 | Monitor.TryEnter(this, ref haveLock);
148 | if (haveLock)
149 | {
150 | if (token == _version)
151 | {
152 | _result = default!;
153 | _exception = default;
154 | _task = default;
155 | _isComplete = false;
156 | _source = default;
157 |
158 | switch (++_version)
159 | {
160 | case InitialVersion: // don't wrap all the way around when recycling; could lead to conflicts
161 | case Constant: // don't allow things to *become* constants
162 | break;
163 | default:
164 | Pool>.TryPut(this);
165 | break;
166 | }
167 | }
168 | }
169 | }
170 | finally
171 | {
172 | if (haveLock) Monitor.Exit(this);
173 | }
174 | }
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/src/PooledAwait/Internal/ManualResetValueTaskSourceCore.cs:
--------------------------------------------------------------------------------
1 | #if !PLAT_MRVTSC
2 | using System;
3 | using System.Diagnostics;
4 | using System.Runtime.ExceptionServices;
5 | using System.Runtime.InteropServices;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 | using System.Threading.Tasks.Sources;
9 |
10 | // from: https://raw.githubusercontent.com/dotnet/coreclr/master/src/System.Private.CoreLib/shared/System/Threading/Tasks/Sources/ManualResetValueTaskSourceCore.cs
11 | // original license:
12 |
13 | // Licensed to the .NET Foundation under one or more agreements.
14 | // The .NET Foundation licenses this file to you under the MIT license.
15 | // See the LICENSE file in the project root for more information.
16 |
17 | namespace PooledAwait.Internal
18 | {
19 | internal readonly struct WaitCallbackShim
20 | {
21 | public readonly Action? Continuation;
22 | public readonly object? State;
23 | private WaitCallbackShim(Action? continuation, object? state)
24 | {
25 | Continuation = continuation;
26 | State = state;
27 | }
28 | public static object Create(Action? continuation, object? state)
29 | => Pool.Box(new WaitCallbackShim(continuation, state));
30 |
31 | private void InvokeContinuation() => Continuation?.Invoke(State);
32 |
33 | public static readonly WaitCallback Invoke = state => Pool.UnboxAndReturn(state).InvokeContinuation();
34 | }
35 |
36 | /// Provides the core logic for implementing a manual-reset or .
37 | ///
38 | [StructLayout(LayoutKind.Auto)]
39 | public struct ManualResetValueTaskSourceCore
40 | {
41 | ///
42 | /// The callback to invoke when the operation completes if was called before the operation completed,
43 | /// or if the operation completed before a callback was supplied,
44 | /// or null if a callback hasn't yet been provided and the operation hasn't yet completed.
45 | ///
46 | private Action? _continuation;
47 | /// State to pass to .
48 | private object? _continuationState;
49 | /// to flow to the callback, or null if no flowing is required.
50 | private ExecutionContext? _executionContext;
51 | ///
52 | /// A "captured" or with which to invoke the callback,
53 | /// or null if no special context is required.
54 | ///
55 | private object? _capturedContext;
56 | /// Whether the current operation has completed.
57 | private bool _completed;
58 | /// The result with which the operation succeeded, or the default value if it hasn't yet completed or failed.
59 | /* [AllowNull, MaybeNull] */ private TResult _result;
60 | /// The exception with which the operation failed, or null if it hasn't yet completed or completed successfully.
61 | private ExceptionDispatchInfo? _error;
62 | /// The current version of this value, used to help prevent misuse.
63 | private short _version;
64 |
65 | /// Gets or sets whether to force continuations to run asynchronously.
66 | /// Continuations may run asynchronously if this is false, but they'll never run synchronously if this is true.
67 | public bool RunContinuationsAsynchronously { get; set; }
68 |
69 | /// Resets to prepare for the next operation.
70 | public void Reset()
71 | {
72 | // Reset/update state for the next use/await of this instance.
73 | _version++;
74 | _completed = false;
75 | _result = default!; // TODO-NULLABLE: Remove ! when nullable attributes are respected
76 | _error = null;
77 | _executionContext = null;
78 | _capturedContext = null;
79 | _continuation = null;
80 | _continuationState = null;
81 | }
82 |
83 | /// Completes with a successful result.
84 | /// The result.
85 | public void SetResult(TResult result)
86 | {
87 | _result = result;
88 | SignalCompletion();
89 | }
90 |
91 | /// Complets with an error.
92 | ///
93 | public void SetException(Exception error)
94 | {
95 | _error = ExceptionDispatchInfo.Capture(error);
96 | SignalCompletion();
97 | }
98 |
99 | /// Gets the operation version.
100 | public short Version => _version;
101 |
102 | /// Gets the status of the operation.
103 | /// Opaque value that was provided to the 's constructor.
104 | public ValueTaskSourceStatus GetStatus(short token)
105 | {
106 | ValidateToken(token);
107 | return
108 | _continuation == null || !_completed ? ValueTaskSourceStatus.Pending :
109 | _error == null ? ValueTaskSourceStatus.Succeeded :
110 | _error.SourceException is OperationCanceledException ? ValueTaskSourceStatus.Canceled :
111 | ValueTaskSourceStatus.Faulted;
112 | }
113 |
114 | /// Gets the result of the operation.
115 | /// Opaque value that was provided to the 's constructor.
116 | // [StackTraceHidden]
117 | public TResult GetResult(short token)
118 | {
119 | ValidateToken(token);
120 | if (!_completed)
121 | {
122 | ThrowHelper.ThrowInvalidOperationException();
123 | }
124 |
125 | _error?.Throw();
126 | return _result;
127 | }
128 |
129 | /// Schedules the continuation action for this operation.
130 | /// The continuation to invoke when the operation has completed.
131 | /// The state object to pass to when it's invoked.
132 | /// Opaque value that was provided to the 's constructor.
133 | /// The flags describing the behavior of the continuation.
134 | public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
135 | {
136 | if (continuation == null) ThrowHelper.ThrowArgumentNullException(nameof(continuation));
137 | ValidateToken(token);
138 |
139 | if ((flags & ValueTaskSourceOnCompletedFlags.FlowExecutionContext) != 0)
140 | {
141 | _executionContext = ExecutionContext.Capture();
142 | }
143 |
144 | if ((flags & ValueTaskSourceOnCompletedFlags.UseSchedulingContext) != 0)
145 | {
146 | SynchronizationContext? sc = SynchronizationContext.Current;
147 | if (sc != null && sc.GetType() != typeof(SynchronizationContext))
148 | {
149 | _capturedContext = sc;
150 | }
151 | else
152 | {
153 | TaskScheduler ts = TaskScheduler.Current;
154 | if (ts != TaskScheduler.Default)
155 | {
156 | _capturedContext = ts;
157 | }
158 | }
159 | }
160 |
161 | // We need to set the continuation state before we swap in the delegate, so that
162 | // if there's a race between this and SetResult/Exception and SetResult/Exception
163 | // sees the _continuation as non-null, it'll be able to invoke it with the state
164 | // stored here. However, this also means that if this is used incorrectly (e.g.
165 | // awaited twice concurrently), _continuationState might get erroneously overwritten.
166 | // To minimize the chances of that, we check preemptively whether _continuation
167 | // is already set to something other than the completion sentinel.
168 |
169 | object? oldContinuation = _continuation;
170 | if (oldContinuation == null)
171 | {
172 | _continuationState = state;
173 | oldContinuation = Interlocked.CompareExchange(ref _continuation, continuation, null);
174 | }
175 |
176 | if (oldContinuation != null)
177 | {
178 | // Operation already completed, so we need to queue the supplied callback.
179 | if (!ReferenceEquals(oldContinuation, ManualResetValueTaskSourceCoreShared.s_sentinel))
180 | {
181 | ThrowHelper.ThrowInvalidOperationException();
182 | }
183 |
184 | switch (_capturedContext)
185 | {
186 | case null:
187 | if (_executionContext != null)
188 | {
189 | ThreadPool.QueueUserWorkItem(WaitCallbackShim.Invoke, WaitCallbackShim.Create(continuation, state));
190 | }
191 | else
192 | {
193 | #if NETSTANDARD1_3
194 | ThreadPool.QueueUserWorkItem(
195 | #else
196 | ThreadPool.UnsafeQueueUserWorkItem(
197 | #endif
198 | WaitCallbackShim.Invoke, WaitCallbackShim.Create(continuation, state));
199 | }
200 | break;
201 |
202 | case SynchronizationContext sc:
203 | sc.Post(s =>
204 | {
205 | var tuple = (Tuple, object?>)s!;
206 | tuple.Item1(tuple.Item2);
207 | }, Tuple.Create(continuation, state));
208 | break;
209 |
210 | case TaskScheduler ts:
211 | Task.Factory.StartNew(continuation, state, CancellationToken.None, TaskCreationOptions.DenyChildAttach, ts);
212 | break;
213 | }
214 | }
215 | }
216 |
217 | /// Ensures that the specified token matches the current version.
218 | /// The token supplied by .
219 | private void ValidateToken(short token)
220 | {
221 | if (token != _version)
222 | {
223 | ThrowHelper.ThrowInvalidOperationException();
224 | }
225 | }
226 |
227 | /// Signals that the operation has completed. Invoked after the result or error has been set.
228 | private void SignalCompletion()
229 | {
230 | if (_completed)
231 | {
232 | ThrowHelper.ThrowInvalidOperationException();
233 | }
234 | _completed = true;
235 |
236 | if (_continuation != null || Interlocked.CompareExchange(ref _continuation, ManualResetValueTaskSourceCoreShared.s_sentinel, null) != null)
237 | {
238 | if (_executionContext != null)
239 | {
240 | ExecutionContext.Run(
241 | _executionContext,
242 | s_UnboxAndInvokeContextCallback,
243 | Pool.Box>(this));
244 | }
245 | else
246 | {
247 | InvokeContinuation();
248 | }
249 | }
250 | }
251 |
252 | static readonly ContextCallback s_UnboxAndInvokeContextCallback = state => Pool.UnboxAndReturn>(state).InvokeContinuation();
253 |
254 | ///
255 | /// Invokes the continuation with the appropriate captured context / scheduler.
256 | /// This assumes that if is not null we're already
257 | /// running within that .
258 | ///
259 | private void InvokeContinuation()
260 | {
261 | Debug.Assert(_continuation != null);
262 |
263 | switch (_capturedContext)
264 | {
265 | case null:
266 | if (RunContinuationsAsynchronously)
267 | {
268 | if (_executionContext != null)
269 | {
270 | ThreadPool.QueueUserWorkItem(WaitCallbackShim.Invoke, WaitCallbackShim.Create(_continuation, _continuationState));
271 | }
272 | else
273 | {
274 | #if NETSTANDARD1_3
275 | ThreadPool.QueueUserWorkItem(
276 | #else
277 | ThreadPool.UnsafeQueueUserWorkItem(
278 | #endif
279 | WaitCallbackShim.Invoke, WaitCallbackShim.Create(_continuation, _continuationState));
280 | }
281 | }
282 | else
283 | {
284 | _continuation!(_continuationState);
285 | }
286 | break;
287 |
288 | case SynchronizationContext sc:
289 | sc.Post(s =>
290 | {
291 | var state = (Tuple, object?>)s!;
292 | state.Item1(state.Item2);
293 | }, Tuple.Create(_continuation, _continuationState));
294 | break;
295 |
296 | case TaskScheduler ts:
297 | Task.Factory.StartNew(_continuation, _continuationState, CancellationToken.None, TaskCreationOptions.DenyChildAttach, ts);
298 | break;
299 | }
300 | }
301 | }
302 |
303 | internal static class ManualResetValueTaskSourceCoreShared // separated out of generic to avoid unnecessary duplication
304 | {
305 | internal static readonly Action s_sentinel = CompletionSentinel;
306 | private static void CompletionSentinel(object? _) // named method to aid debugging
307 | {
308 | Debug.Fail("The sentinel delegate should never be invoked.");
309 | ThrowHelper.ThrowInvalidOperationException();
310 | }
311 | }
312 | }
313 | #endif
314 |
--------------------------------------------------------------------------------
/src/PooledAwait/Internal/Nothing.cs:
--------------------------------------------------------------------------------
1 | namespace PooledAwait.Internal
2 | {
3 | internal readonly struct Nothing // to express ValueTask via PooledState
4 | {
5 | public override string ToString() => nameof(Nothing);
6 | public override int GetHashCode() => 0;
7 | public override bool Equals(object? obj) => obj is Nothing;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/src/PooledAwait/Internal/PooledStateT.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 | using System.Threading;
4 | using System.Threading.Tasks.Sources;
5 |
6 | namespace PooledAwait.Internal
7 | {
8 | internal sealed class PooledState : IValueTaskSource, IValueTaskSource
9 | {
10 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
11 | public static PooledState Create(out short token)
12 | {
13 | var obj = Pool>.TryGet() ?? new PooledState();
14 | token = obj._source.Version;
15 | return obj;
16 | }
17 |
18 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
19 | private PooledState() => Counters.PooledStateAllocated.Increment();
20 |
21 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
22 | internal bool IsValid(short token) => _source.Version == token;
23 |
24 | private ManualResetValueTaskSourceCore _source; // needs to be mutable
25 |
26 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
27 | public T GetResult(short token)
28 | {
29 | // we only support getting the result once; doing this recycles the source and advances the token
30 |
31 | lock (SyncLock) // we need to be really paranoid about cross-threading over changing the token
32 | {
33 | var status = _source.GetStatus(token); // do this *outside* the try/finally
34 | try // so that we don't increment the counter if someone asks for the wrong value
35 | {
36 | switch(status)
37 | {
38 | case ValueTaskSourceStatus.Canceled:
39 | ThrowHelper.ThrowTaskCanceledException();
40 | break;
41 | case ValueTaskSourceStatus.Pending:
42 | Monitor.Wait(SyncLock);
43 | break;
44 | }
45 | return _source.GetResult(token);
46 | }
47 | finally
48 | {
49 | _source.Reset();
50 | if (_source.Version != TaskUtils.InitialTaskSourceVersion)
51 | {
52 | Pool>.TryPut(this);
53 | Counters.PooledStateRecycled.Increment();
54 | }
55 | }
56 | }
57 | }
58 |
59 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
60 | void IValueTaskSource.GetResult(short token) => GetResult(token);
61 |
62 | [MethodImpl(MethodImplOptions.NoInlining)]
63 | private void SignalResult(short token)
64 | {
65 | lock (SyncLock)
66 | {
67 | if (token == _source.Version && _source.GetStatus(token) != ValueTaskSourceStatus.Pending)
68 | {
69 | Monitor.Pulse(SyncLock);
70 | }
71 | }
72 | }
73 |
74 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
75 | public ValueTaskSourceStatus GetStatus(short token) => _source.GetStatus(token);
76 |
77 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
78 | public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags)
79 | => _source.OnCompleted(continuation, state, token, flags);
80 |
81 | private object SyncLock
82 | {
83 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
84 | get => this;
85 | }
86 |
87 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
88 | public bool TrySetException(Exception error, short token)
89 | {
90 | if (token == _source.Version)
91 | {
92 |
93 | switch (_source.GetStatus(token))
94 | {
95 | case ValueTaskSourceStatus.Pending:
96 | _source.SetException(error);
97 | // only need to signal if SetException didn't inline a handler
98 | if (token == _source.Version) SignalResult(token);
99 | return true;
100 | }
101 | }
102 | return false;
103 | }
104 |
105 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
106 | public bool TrySetResult(T result, short token)
107 | {
108 | if (token == _source.Version)
109 | {
110 | switch (_source.GetStatus(token))
111 | {
112 | case ValueTaskSourceStatus.Pending:
113 | _source.SetResult(result);
114 | // only need to signal if SetResult didn't inline a handler
115 | if (token == _source.Version) SignalResult(token);
116 | return true;
117 | }
118 | }
119 | return false;
120 | }
121 |
122 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
123 | public bool TrySetCanceled(short token)
124 | => TrySetException(TaskUtils.SharedTaskCanceledException, token);
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/PooledAwait/Internal/StateMachineBox.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | namespace PooledAwait.Internal
7 | {
8 | internal sealed class StateMachineBox
9 | #if PLAT_THREADPOOLWORKITEM
10 | : IThreadPoolWorkItem
11 | #endif
12 | where TStateMachine : IAsyncStateMachine
13 | {
14 | private readonly Action _execute;
15 | private TStateMachine _stateMachine;
16 |
17 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
18 | private StateMachineBox()
19 | {
20 | _stateMachine = default!;
21 | _execute = Execute;
22 | Counters.StateMachineBoxAllocated.Increment();
23 | }
24 |
25 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
26 | private static StateMachineBox Create(ref TStateMachine stateMachine)
27 | {
28 | var box = Pool>.TryGet() ?? new StateMachineBox();
29 | box._stateMachine = stateMachine;
30 | return box;
31 | }
32 |
33 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
34 | public static void AwaitOnCompleted(
35 | ref TAwaiter awaiter, ref TStateMachine stateMachine)
36 | where TAwaiter : INotifyCompletion
37 | {
38 | var box = Create(ref stateMachine);
39 | if (typeof(TAwaiter) == typeof(YieldAwaitable.YieldAwaiter))
40 | {
41 | Yield(box, true);
42 | }
43 | else
44 | {
45 | awaiter.OnCompleted(box._execute);
46 | }
47 | }
48 |
49 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
50 | public static void AwaitUnsafeOnCompleted(
51 | ref TAwaiter awaiter, ref TStateMachine stateMachine)
52 | where TAwaiter : ICriticalNotifyCompletion
53 | {
54 | var box = Create(ref stateMachine);
55 | if (typeof(TAwaiter) == typeof(YieldAwaitable.YieldAwaiter))
56 | {
57 | Yield(box, false);
58 | }
59 | else
60 | {
61 | awaiter.UnsafeOnCompleted(box._execute);
62 | }
63 | }
64 |
65 | private static void Yield(StateMachineBox box, bool flowContext)
66 | {
67 | // heavily inspired by YieldAwaitable.QueueContinuation
68 |
69 | var syncContext = SynchronizationContext.Current;
70 | if (syncContext != null && syncContext.GetType() != typeof(SynchronizationContext))
71 | {
72 | syncContext.Post(s_SendOrPostCallback, box);
73 | }
74 | else
75 | {
76 | var taskScheduler = TaskScheduler.Current;
77 | if (!ReferenceEquals(taskScheduler, TaskScheduler.Default))
78 | {
79 | Task.Factory.StartNew(box._execute, default, TaskCreationOptions.PreferFairness, taskScheduler);
80 | }
81 | else if (flowContext)
82 | {
83 | ThreadPool.QueueUserWorkItem(s_WaitCallback, box);
84 | }
85 | else
86 | {
87 | #if PLAT_THREADPOOLWORKITEM
88 | ThreadPool.UnsafeQueueUserWorkItem(box, false);
89 | #elif NETSTANDARD1_3
90 | ThreadPool.QueueUserWorkItem(s_WaitCallback, box);
91 | #else
92 | ThreadPool.UnsafeQueueUserWorkItem(s_WaitCallback, box);
93 | #endif
94 | }
95 | }
96 | }
97 |
98 | static readonly SendOrPostCallback s_SendOrPostCallback = state => ((StateMachineBox?)state!)?.Execute();
99 | static readonly WaitCallback s_WaitCallback = state => ((StateMachineBox?)state)?.Execute();
100 |
101 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
102 | public void Execute()
103 | {
104 | // extract the state
105 | var tmp = _stateMachine;
106 |
107 | // recycle the instance
108 | _stateMachine = default!;
109 | Pool>.TryPut(this);
110 | Counters.StateMachineBoxRecycled.Increment();
111 |
112 | // progress the state machine
113 | tmp.MoveNext();
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/PooledAwait/Internal/TaskUtils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Runtime.CompilerServices;
4 | using System.Threading.Tasks;
5 | using System.Threading.Tasks.Sources;
6 |
7 | namespace PooledAwait.Internal
8 | {
9 | // NET45 lacks some useful Task APIs; shim over them
10 | internal static class TaskUtils
11 | {
12 | internal static readonly short InitialTaskSourceVersion = new ManualResetValueTaskSourceCore().Version;
13 |
14 | public static readonly TaskCanceledException SharedTaskCanceledException = new TaskCanceledException();
15 | #if NET45
16 | public static readonly Task CompletedTask = TaskFactory.FromResult(default);
17 |
18 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
19 | public static Task FromException(Exception exception)
20 | {
21 | var source = ValueTaskCompletionSource.Create();
22 | source.TrySetException(exception);
23 | return source.Task;
24 | }
25 |
26 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
27 | public static Task FromException(Exception exception) => FromException(exception);
28 | #else
29 | public static readonly Task CompletedTask = Task.CompletedTask;
30 |
31 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
32 | public static Task FromException(Exception exception) => Task.FromException(exception);
33 |
34 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
35 | public static Task FromException(Exception exception) => Task.FromException(exception);
36 | #endif
37 |
38 |
39 | internal static class TaskFactory
40 | {
41 | // draws from AsyncMethodBuilder, but less boxing
42 |
43 | public static readonly Task Canceled = CreateCanceled();
44 |
45 | static Task CreateCanceled()
46 | {
47 | var source = ValueTaskCompletionSource.Create();
48 | source.TrySetCanceled();
49 | return source.Task;
50 | }
51 |
52 | private static readonly TaskCache _cache = (TaskCache)CreateCacheForType();
53 |
54 | private static object CreateCacheForType()
55 | {
56 | if (typeof(TResult) == typeof(Nothing)) return new NothingTaskCache();
57 | if (typeof(TResult) == typeof(int)) return new Int32TaskCache();
58 | if (typeof(TResult) == typeof(int?)) return new NullableInt32TaskCache();
59 | if (typeof(TResult) == typeof(bool)) return new BooleanTaskCache();
60 | if (typeof(TResult) == typeof(bool?)) return new NullableBooleanTaskCache();
61 |
62 | Type underlyingType = Nullable.GetUnderlyingType(typeof(TResult)) ?? typeof(TResult);
63 | if (underlyingType == typeof(uint)
64 | || underlyingType == typeof(byte)
65 | || underlyingType == typeof(sbyte)
66 | || underlyingType == typeof(char)
67 | || underlyingType == typeof(decimal)
68 | || underlyingType == typeof(long)
69 | || underlyingType == typeof(ulong)
70 | || underlyingType == typeof(short)
71 | || underlyingType == typeof(ushort)
72 | || underlyingType == typeof(float)
73 | || underlyingType == typeof(double)
74 | || underlyingType == typeof(IntPtr)
75 | || underlyingType == typeof(UIntPtr)
76 | ) return new DefaultEquatableTaskCache();
77 |
78 | if (typeof(TResult) == typeof(string)) return new StringTaskCache();
79 | #if !NETSTANDARD1_3
80 | if (!typeof(TResult).IsValueType) return new ObjectTaskCache();
81 | #endif
82 | return new TaskCache();
83 | }
84 |
85 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
86 | public static Task FromResult(TResult result) => _cache.FromResult(result);
87 | }
88 |
89 | class TaskCache
90 | {
91 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
92 | internal virtual Task FromResult(TResult value) => Task.FromResult(value);
93 | }
94 | class NothingTaskCache : TaskCache
95 | {
96 | private static readonly Task s_Instance = Task.FromResult(default);
97 | internal override Task FromResult(Nothing value) => s_Instance;
98 | }
99 | class DefaultEquatableTaskCache : TaskCache
100 | {
101 | private static readonly Task s_Default = Task.FromResult(default!);
102 | private static readonly EqualityComparer _comparer = EqualityComparer.Default;
103 | internal override Task FromResult(TResult value)
104 | => _comparer.Equals(value, default!) ? s_Default : base.FromResult(value);
105 | }
106 | class ObjectTaskCache : TaskCache
107 | {
108 | private static readonly Task s_Null = Task.FromResult(default!);
109 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
110 | internal override Task FromResult(TResult value)
111 | => value == null ? s_Null : base.FromResult(value);
112 | }
113 | sealed class StringTaskCache : ObjectTaskCache
114 | {
115 | private static readonly Task s_Empty = Task.FromResult("");
116 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
117 | internal override Task FromResult(string value)
118 | => value == "" ? s_Empty : base.FromResult(value);
119 | }
120 | sealed class BooleanTaskCache : TaskCache
121 | {
122 | static readonly Task s_True = Task.FromResult(true), s_False = Task.FromResult(false);
123 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
124 | internal override Task FromResult(bool value) => value ? s_True : s_False;
125 | }
126 | sealed class NullableBooleanTaskCache : TaskCache
127 | {
128 | static readonly Task s_True = Task.FromResult((bool?)true), s_False = Task.FromResult((bool?)false),
129 | s_Null = Task.FromResult((bool?)null);
130 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
131 | internal override Task FromResult(bool? value) =>
132 | value.HasValue ? (value.GetValueOrDefault() ? s_True : s_False) : s_Null;
133 | }
134 | sealed class Int32TaskCache : TaskCache
135 | {
136 | const int MIN_INC = -1, MAX_EXC = 11;
137 | static readonly Task[] s_Known = CreateKnown();
138 | static Task[] CreateKnown()
139 | {
140 | var arr = new Task[MAX_EXC - MIN_INC];
141 | for (int i = 0; i < arr.Length; i++)
142 | arr[i] = Task.FromResult(i + MIN_INC);
143 | return arr;
144 | }
145 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
146 | internal override Task FromResult(int value)
147 | => value >= MIN_INC && value < MAX_EXC ? s_Known[value - MIN_INC] : base.FromResult(value);
148 | }
149 | sealed class NullableInt32TaskCache : TaskCache
150 | {
151 | const int MIN_INC = -1, MAX_EXC = 11;
152 | static readonly Task[] s_Known = CreateKnown();
153 | static readonly Task s_Null = Task.FromResult((int?)null);
154 | static Task[] CreateKnown()
155 | {
156 | var arr = new Task[MAX_EXC - MIN_INC];
157 | for (int i = 0; i < arr.Length; i++)
158 | arr[i] = Task.FromResult((int?)(i + MIN_INC));
159 | return arr;
160 | }
161 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
162 | internal override Task FromResult(int? nullable)
163 | {
164 | if (nullable.HasValue)
165 | {
166 | int value = nullable.GetValueOrDefault();
167 | return value >= MIN_INC && value < MAX_EXC ? s_Known[value - MIN_INC] : base.FromResult(nullable);
168 | }
169 | return s_Null;
170 | }
171 | }
172 |
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/src/PooledAwait/Internal/ThrowHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.CompilerServices;
3 | using System.Threading.Tasks;
4 |
5 | namespace PooledAwait.Internal
6 | {
7 | internal static class ThrowHelper
8 | {
9 | [MethodImpl(MethodImplOptions.NoInlining)]
10 | internal static void ThrowInvalidOperationException(string? message = null)
11 | {
12 | if (string.IsNullOrWhiteSpace(message)) throw new InvalidOperationException();
13 | else throw new InvalidOperationException(message);
14 | }
15 |
16 | [MethodImpl(MethodImplOptions.NoInlining)]
17 | internal static T ThrowInvalidOperationException(string? message = null)
18 | {
19 | ThrowInvalidOperationException(message);
20 | return default!;
21 | }
22 |
23 | [MethodImpl(MethodImplOptions.NoInlining)]
24 | internal static T ThrowNotSupportedException() => throw new NotSupportedException();
25 |
26 | [MethodImpl(MethodImplOptions.NoInlining)]
27 | internal static void ThrowArgumentNullException(string paramName) => throw new ArgumentNullException(paramName);
28 |
29 | [MethodImpl(MethodImplOptions.NoInlining)]
30 | internal static void ThrowTaskCanceledException() => throw new TaskCanceledException();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/PooledAwait/LazyTaskCompletionSource.cs:
--------------------------------------------------------------------------------
1 | using PooledAwait.Internal;
2 | using System;
3 | using System.Runtime.CompilerServices;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 |
7 | namespace PooledAwait
8 | {
9 | ///
10 | /// Like a ValueTaskCompletionSource, but the actual task will only become allocated
11 | /// if the .Task is consumed; this is useful for APIs where the consumer *may* consume a task
12 | ///
13 | public readonly struct LazyTaskCompletionSource : IDisposable
14 | {
15 |
16 | private static LazyTaskCompletionSource _completed, _canceled;
17 |
18 | ///
19 | /// A global LazyTaskCompletionSource that represents a completed operation
20 | ///
21 | public static LazyTaskCompletionSource CompletedTask
22 | {
23 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
24 | get => _completed.IsValid ? _completed : _completed = new LazyTaskCompletionSource(LazyTaskState.CreateConstant(default));
25 | }
26 |
27 | ///
28 | /// A global LazyTaskCompletionSource that represents a cancelled operation
29 | ///
30 | public static LazyTaskCompletionSource CanceledTask
31 | {
32 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
33 | get => _canceled.IsValid ? _canceled : _canceled = new LazyTaskCompletionSource(LazyTaskState.CreateCanceled());
34 | }
35 |
36 | ///
37 | public override bool Equals(object? obj) => obj is LazyTaskCompletionSource ltcs && _state == ltcs._state && _token == ltcs._token;
38 | ///
39 | public override int GetHashCode() => (_state == null ? 0 : _state.GetHashCode()) ^ _token;
40 | ///
41 | public override string ToString() => nameof(LazyTaskCompletionSource);
42 |
43 | private readonly LazyTaskState _state;
44 | private readonly short _token;
45 |
46 | ///
47 | /// Gets the task associated with this instance
48 | ///
49 | public Task Task => _state?.GetTask(_token) ?? ThrowHelper.ThrowInvalidOperationException();
50 |
51 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
52 | private LazyTaskCompletionSource(LazyTaskState state)
53 | {
54 | _state = state;
55 | _token = state.Version;
56 | }
57 |
58 | ///
59 | /// Create a new instance; this instance should be disposed when it is known to be unwanted
60 | ///
61 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
62 | public static LazyTaskCompletionSource Create()
63 | => new LazyTaskCompletionSource(LazyTaskState.Create());
64 |
65 | ///
66 | /// Set the result of the operation
67 | ///
68 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
69 | public bool TrySetResult() => _state != null && _state.TrySetResult(_token, default);
70 |
71 | ///
72 | /// Set the result of the operation
73 | ///
74 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
75 | public bool TrySetCanceled(CancellationToken cancellationToken = default) => _state != null && _state.TrySetCanceled(_token, cancellationToken);
76 |
77 | ///
78 | /// Set the result of the operation
79 | ///
80 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
81 | public void SetResult()
82 | {
83 | if (!TrySetResult()) ThrowHelper.ThrowInvalidOperationException();
84 | }
85 |
86 | ///
87 | /// Set the result of the operation
88 | ///
89 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
90 | public void SetCanceled(CancellationToken cancellationToken = default)
91 | {
92 | if (!TrySetCanceled(cancellationToken)) ThrowHelper.ThrowInvalidOperationException();
93 | }
94 |
95 | ///
96 | /// Set the result of the operation
97 | ///
98 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
99 | public bool TrySetException(Exception exception) => _state != null && _state.TrySetException(_token, exception);
100 |
101 | ///
102 | /// Set the result of the operation
103 | ///
104 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
105 | public void SetException(Exception exception)
106 | {
107 | if (!TrySetException(exception)) ThrowHelper.ThrowInvalidOperationException();
108 | }
109 |
110 | ///
111 | /// Release all resources associated with this operation
112 | ///
113 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
114 | public void Dispose() => _state?.Recycle(_token);
115 |
116 | internal bool IsValid => _state != null && _state.IsValid(_token);
117 | internal bool HasSource => _state != null && _state.HasSource;
118 | internal bool HasTask => _state != null && _state.HasTask;
119 |
120 | ///
121 | /// Indicates whether this is an invalid default instance
122 | ///
123 | public bool IsNull
124 | {
125 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
126 | get =>_state == null;
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/PooledAwait/LazyTaskCompletionSourceT.cs:
--------------------------------------------------------------------------------
1 | using PooledAwait.Internal;
2 | using System;
3 | using System.Runtime.CompilerServices;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 |
7 | namespace PooledAwait
8 | {
9 | ///
10 | /// Like a ValueTaskCompletionSource , but the actual task will only become allocated
11 | /// if the .Task is consumed; this is useful for APIs where the consumer *may* consume a task
12 | ///
13 | public readonly struct LazyTaskCompletionSource : IDisposable
14 | {
15 | ///
16 | public override bool Equals(object? obj) => obj is LazyTaskCompletionSource ltcs && _state == ltcs._state && _token == ltcs._token;
17 | ///
18 | public override int GetHashCode() => (_state == null ? 0 : _state.GetHashCode()) ^ _token;
19 | ///
20 | public override string ToString() => nameof(LazyTaskCompletionSource);
21 |
22 | private readonly LazyTaskState _state;
23 | private readonly short _token;
24 |
25 | ///
26 | /// Gets the task associated with this instance
27 | ///
28 | public Task Task => (Task)(_state?.GetTask(_token) ?? ThrowHelper.ThrowInvalidOperationException>());
29 |
30 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
31 | private LazyTaskCompletionSource(LazyTaskState state)
32 | {
33 | _state = state;
34 | _token = state.Version;
35 | }
36 |
37 | ///
38 | /// Create a new instance; this instance should be disposed when it is known to be unwanted
39 | ///
40 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
41 | public static LazyTaskCompletionSource Create()
42 | => new LazyTaskCompletionSource(LazyTaskState.Create());
43 |
44 | ///
45 | /// Create a new instance; this instance will never by recycled
46 | ///
47 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
48 | public static LazyTaskCompletionSource CreateConstant(T value)
49 | => new LazyTaskCompletionSource(LazyTaskState.CreateConstant(value));
50 |
51 |
52 | private static LazyTaskCompletionSource _canceled;
53 |
54 | ///
55 | /// A global LazyTaskCompletionSource that represents a cancelled operation
56 | ///
57 | public static LazyTaskCompletionSource CanceledTask
58 | {
59 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
60 | get => _canceled.IsValid ? _canceled : _canceled = new LazyTaskCompletionSource(LazyTaskState.CreateCanceled());
61 | }
62 |
63 | ///
64 | /// Set the result of the operation
65 | ///
66 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
67 | public bool TrySetResult(T result) => _state != null && _state.TrySetResult(_token, result);
68 |
69 | ///
70 | /// Set the result of the operation
71 | ///
72 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
73 | public void SetResult(T result)
74 | {
75 | if (!TrySetResult(result)) ThrowHelper.ThrowInvalidOperationException();
76 | }
77 |
78 | ///
79 | /// Set the result of the operation
80 | ///
81 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
82 | public bool TrySetCanceled(CancellationToken cancellationToken = default)
83 | => _state != null && _state.TrySetCanceled(_token, cancellationToken);
84 |
85 | ///
86 | /// Set the result of the operation
87 | ///
88 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
89 | public void SetCanceled(CancellationToken cancellationToken = default)
90 | {
91 | if (!TrySetCanceled(cancellationToken)) ThrowHelper.ThrowInvalidOperationException();
92 | }
93 |
94 | ///
95 | /// Set the result of the operation
96 | ///
97 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
98 | public bool TrySetException(Exception exception) => _state != null && _state.TrySetException(_token, exception);
99 |
100 | ///
101 | /// Set the result of the operation
102 | ///
103 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
104 | public void SetException(Exception exception)
105 | {
106 | if (!TrySetException(exception)) ThrowHelper.ThrowInvalidOperationException();
107 | }
108 |
109 | ///
110 | /// Release all resources associated with this operation
111 | ///
112 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
113 | public void Dispose() => _state?.Recycle(_token);
114 |
115 | internal bool IsValid => _state != null && _state.IsValid(_token);
116 | internal bool HasSource => _state != null && _state.HasSource;
117 | internal bool HasTask => _state != null && _state.HasTask;
118 |
119 | ///
120 | /// Indicates whether this is an invalid default instance
121 | ///
122 | public bool IsNull
123 | {
124 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
125 | get => _state == null;
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/PooledAwait/MethodBuilders/FireAndForgetMethodBuilder.cs:
--------------------------------------------------------------------------------
1 | using PooledAwait.Internal;
2 | using System;
3 | using System.ComponentModel;
4 | using System.Runtime.CompilerServices;
5 |
6 | #pragma warning disable CS1591
7 |
8 | namespace PooledAwait.MethodBuilders
9 | {
10 | ///
11 | /// This type is not intended for direct usage
12 | ///
13 | [Browsable(false)]
14 | [EditorBrowsable(EditorBrowsableState.Never)]
15 | public struct FireAndForgetMethodBuilder
16 | {
17 | public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException();
18 | public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException();
19 | public override string ToString() => nameof(FireAndForgetMethodBuilder);
20 |
21 | [Browsable(false)]
22 | [EditorBrowsable(EditorBrowsableState.Never)]
23 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
24 | public static FireAndForgetMethodBuilder Create() => default;
25 |
26 | [Browsable(false)]
27 | [EditorBrowsable(EditorBrowsableState.Never)]
28 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
29 | public void SetStateMachine(IAsyncStateMachine _) => Counters.SetStateMachine.Increment();
30 |
31 | [Browsable(false)]
32 | [EditorBrowsable(EditorBrowsableState.Never)]
33 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
34 | public void SetResult() { }
35 |
36 | [Browsable(false)]
37 | [EditorBrowsable(EditorBrowsableState.Never)]
38 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
39 | public void SetException(Exception exception) => FireAndForget.OnException(exception);
40 |
41 | [Browsable(false)]
42 | [EditorBrowsable(EditorBrowsableState.Never)]
43 | public FireAndForget Task
44 | {
45 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
46 | get => default;
47 | }
48 |
49 | [Browsable(false)]
50 | [EditorBrowsable(EditorBrowsableState.Never)]
51 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
52 | public void AwaitOnCompleted(
53 | ref TAwaiter awaiter, ref TStateMachine stateMachine)
54 | where TAwaiter : INotifyCompletion
55 | where TStateMachine : IAsyncStateMachine
56 | => StateMachineBox.AwaitOnCompleted(ref awaiter, ref stateMachine);
57 |
58 | [Browsable(false)]
59 | [EditorBrowsable(EditorBrowsableState.Never)]
60 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
61 | public void AwaitUnsafeOnCompleted(
62 | ref TAwaiter awaiter, ref TStateMachine stateMachine)
63 | where TAwaiter : ICriticalNotifyCompletion
64 | where TStateMachine : IAsyncStateMachine
65 | => StateMachineBox.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
66 |
67 | [Browsable(false)]
68 | [EditorBrowsable(EditorBrowsableState.Never)]
69 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
70 | public void Start(ref TStateMachine stateMachine)
71 | where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext();
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/PooledAwait/MethodBuilders/PooledTaskMethodBuilder.cs:
--------------------------------------------------------------------------------
1 | using PooledAwait.Internal;
2 | using System;
3 | using System.ComponentModel;
4 | using System.Runtime.CompilerServices;
5 | using SystemTask = System.Threading.Tasks.Task;
6 |
7 | #pragma warning disable CS1591
8 |
9 | namespace PooledAwait.MethodBuilders
10 | {
11 | ///
12 | /// This type is not intended for direct usage
13 | ///
14 | [Browsable(false)]
15 | [EditorBrowsable(EditorBrowsableState.Never)]
16 | public struct PooledTaskMethodBuilder
17 | {
18 | public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException();
19 | public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException();
20 | public override string ToString() => nameof(PooledTaskMethodBuilder);
21 |
22 | private ValueTaskCompletionSource _source;
23 | private Exception _exception;
24 |
25 | [Browsable(false)]
26 | [EditorBrowsable(EditorBrowsableState.Never)]
27 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
28 | public static PooledTaskMethodBuilder Create() => default;
29 |
30 | [Browsable(false)]
31 | [EditorBrowsable(EditorBrowsableState.Never)]
32 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
33 | public void SetStateMachine(IAsyncStateMachine _) => Counters.SetStateMachine.Increment();
34 |
35 | [Browsable(false)]
36 | [EditorBrowsable(EditorBrowsableState.Never)]
37 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
38 | public void SetResult()
39 | {
40 | _source.TrySetResult(default);
41 | }
42 |
43 | [Browsable(false)]
44 | [EditorBrowsable(EditorBrowsableState.Never)]
45 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
46 | public void SetException(Exception exception)
47 | {
48 | _source.TrySetException(exception);
49 | _exception = exception;
50 | }
51 |
52 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
53 | private void EnsureHasTask()
54 | {
55 | if (_source.IsNull) _source = ValueTaskCompletionSource.Create();
56 | }
57 |
58 | [Browsable(false)]
59 | [EditorBrowsable(EditorBrowsableState.Never)]
60 | public PooledTask Task
61 | {
62 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
63 | get
64 | {
65 | SystemTask task;
66 | if (!_source.IsNull) task = _source.Task;
67 | else if (_exception is OperationCanceledException) task = TaskUtils.TaskFactory.Canceled;
68 | else if (_exception != null) task = TaskUtils.FromException(_exception);
69 | else task = TaskUtils.CompletedTask;
70 | return new PooledTask(task);
71 | }
72 | }
73 |
74 | [Browsable(false)]
75 | [EditorBrowsable(EditorBrowsableState.Never)]
76 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
77 | public void AwaitOnCompleted(
78 | ref TAwaiter awaiter, ref TStateMachine stateMachine)
79 | where TAwaiter : INotifyCompletion
80 | where TStateMachine : IAsyncStateMachine
81 | {
82 | EnsureHasTask();
83 | StateMachineBox.AwaitOnCompleted(ref awaiter, ref stateMachine);
84 | }
85 |
86 | [Browsable(false)]
87 | [EditorBrowsable(EditorBrowsableState.Never)]
88 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
89 | public void AwaitUnsafeOnCompleted(
90 | ref TAwaiter awaiter, ref TStateMachine stateMachine)
91 | where TAwaiter : ICriticalNotifyCompletion
92 | where TStateMachine : IAsyncStateMachine
93 | {
94 | EnsureHasTask();
95 | StateMachineBox.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
96 | }
97 |
98 | [Browsable(false)]
99 | [EditorBrowsable(EditorBrowsableState.Never)]
100 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
101 | public void Start(ref TStateMachine stateMachine)
102 | where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext();
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/PooledAwait/MethodBuilders/PooledTaskMethodBuilderT.cs:
--------------------------------------------------------------------------------
1 | using PooledAwait.Internal;
2 | using System;
3 | using System.ComponentModel;
4 | using System.Runtime.CompilerServices;
5 | using System.Threading.Tasks;
6 | using SystemTask = System.Threading.Tasks.Task;
7 |
8 | #pragma warning disable CS1591
9 |
10 | namespace PooledAwait.MethodBuilders
11 | {
12 | ///
13 | /// This type is not intended for direct usage
14 | ///
15 | [Browsable(false)]
16 | [EditorBrowsable(EditorBrowsableState.Never)]
17 | public struct PooledTaskMethodBuilder
18 | {
19 | public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException();
20 | public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException();
21 | public override string ToString() => nameof(PooledTaskMethodBuilder);
22 |
23 | private ValueTaskCompletionSource _source;
24 | private Exception _exception;
25 | private T _result;
26 |
27 | [Browsable(false)]
28 | [EditorBrowsable(EditorBrowsableState.Never)]
29 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
30 | public static PooledTaskMethodBuilder Create() => default;
31 |
32 | [Browsable(false)]
33 | [EditorBrowsable(EditorBrowsableState.Never)]
34 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
35 | public void SetStateMachine(IAsyncStateMachine _) => Counters.SetStateMachine.Increment();
36 |
37 | [Browsable(false)]
38 | [EditorBrowsable(EditorBrowsableState.Never)]
39 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
40 | public void SetResult(T result)
41 | {
42 | _source.TrySetResult(result);
43 | _result = result;
44 | }
45 |
46 | [Browsable(false)]
47 | [EditorBrowsable(EditorBrowsableState.Never)]
48 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
49 | public void SetException(Exception exception)
50 | {
51 | _source.TrySetException(exception);
52 | _exception = exception;
53 | }
54 |
55 | [Browsable(false)]
56 | [EditorBrowsable(EditorBrowsableState.Never)]
57 | public PooledTask Task
58 | {
59 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
60 | get
61 | {
62 | Task task;
63 | if (!_source.IsNull) task = _source.Task;
64 | else if (_exception is OperationCanceledException) task = TaskUtils.TaskFactory.Canceled;
65 | else if (_exception != null) task = TaskUtils.FromException(_exception);
66 | else task = TaskUtils.TaskFactory.FromResult(_result);
67 | return new PooledTask(task);
68 | }
69 | }
70 |
71 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
72 | private void EnsureHasTask()
73 | {
74 | if (_source.IsNull) _source = ValueTaskCompletionSource.Create();
75 | }
76 |
77 | [Browsable(false)]
78 | [EditorBrowsable(EditorBrowsableState.Never)]
79 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
80 | public void AwaitOnCompleted(
81 | ref TAwaiter awaiter, ref TStateMachine stateMachine)
82 | where TAwaiter : INotifyCompletion
83 | where TStateMachine : IAsyncStateMachine
84 | {
85 | EnsureHasTask();
86 | StateMachineBox.AwaitOnCompleted(ref awaiter, ref stateMachine);
87 | }
88 |
89 | [Browsable(false)]
90 | [EditorBrowsable(EditorBrowsableState.Never)]
91 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
92 | public void AwaitUnsafeOnCompleted(
93 | ref TAwaiter awaiter, ref TStateMachine stateMachine)
94 | where TAwaiter : ICriticalNotifyCompletion
95 | where TStateMachine : IAsyncStateMachine
96 | {
97 | EnsureHasTask();
98 | StateMachineBox.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
99 | }
100 |
101 | [Browsable(false)]
102 | [EditorBrowsable(EditorBrowsableState.Never)]
103 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
104 | public void Start(ref TStateMachine stateMachine)
105 | where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext();
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/PooledAwait/MethodBuilders/PooledValueTaskMethodBuilder.cs:
--------------------------------------------------------------------------------
1 | using PooledAwait.Internal;
2 | using System;
3 | using System.ComponentModel;
4 | using System.Runtime.CompilerServices;
5 |
6 | #pragma warning disable CS1591
7 |
8 | namespace PooledAwait.MethodBuilders
9 | {
10 | ///
11 | /// This type is not intended for direct usage
12 | ///
13 | [Browsable(false)]
14 | [EditorBrowsable(EditorBrowsableState.Never)]
15 | public struct PooledValueTaskMethodBuilder
16 | {
17 | public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException();
18 | public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException();
19 | public override string ToString() => nameof(PooledValueTaskMethodBuilder);
20 |
21 | private PooledValueTaskSource _source;
22 |
23 | [Browsable(false)]
24 | [EditorBrowsable(EditorBrowsableState.Never)]
25 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
26 | public static PooledValueTaskMethodBuilder Create() => default;
27 |
28 | [Browsable(false)]
29 | [EditorBrowsable(EditorBrowsableState.Never)]
30 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
31 | public void SetStateMachine(IAsyncStateMachine _) => Counters.SetStateMachine.Increment();
32 |
33 | [Browsable(false)]
34 | [EditorBrowsable(EditorBrowsableState.Never)]
35 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
36 | public void SetResult()
37 | {
38 | _source.TrySetResult();
39 | }
40 |
41 | [Browsable(false)]
42 | [EditorBrowsable(EditorBrowsableState.Never)]
43 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
44 | public void SetException(Exception exception)
45 | {
46 | EnsureHasTask();
47 | _source.TrySetException(exception);
48 | }
49 |
50 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
51 | private void EnsureHasTask()
52 | {
53 | if (!_source.HasTask) _source = PooledValueTaskSource.Create();
54 | }
55 |
56 | [Browsable(false)]
57 | [EditorBrowsable(EditorBrowsableState.Never)]
58 | public PooledValueTask Task
59 | {
60 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
61 | get => _source.PooledTask;
62 | }
63 |
64 | [Browsable(false)]
65 | [EditorBrowsable(EditorBrowsableState.Never)]
66 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
67 | public void AwaitOnCompleted(
68 | ref TAwaiter awaiter, ref TStateMachine stateMachine)
69 | where TAwaiter : INotifyCompletion
70 | where TStateMachine : IAsyncStateMachine
71 | {
72 | EnsureHasTask();
73 | StateMachineBox.AwaitOnCompleted(ref awaiter, ref stateMachine);
74 | }
75 |
76 | [Browsable(false)]
77 | [EditorBrowsable(EditorBrowsableState.Never)]
78 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
79 | public void AwaitUnsafeOnCompleted(
80 | ref TAwaiter awaiter, ref TStateMachine stateMachine)
81 | where TAwaiter : ICriticalNotifyCompletion
82 | where TStateMachine : IAsyncStateMachine
83 | {
84 | EnsureHasTask();
85 | StateMachineBox.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
86 | }
87 |
88 | [Browsable(false)]
89 | [EditorBrowsable(EditorBrowsableState.Never)]
90 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
91 | public void Start(ref TStateMachine stateMachine)
92 | where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext();
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/PooledAwait/MethodBuilders/PooledValueTaskMethodBuilderT.cs:
--------------------------------------------------------------------------------
1 | using PooledAwait.Internal;
2 | using System;
3 | using System.ComponentModel;
4 | using System.Runtime.CompilerServices;
5 |
6 | #pragma warning disable CS1591
7 |
8 | namespace PooledAwait.MethodBuilders
9 | {
10 | ///
11 | /// This type is not intended for direct usage
12 | ///
13 | [Browsable(false)]
14 | [EditorBrowsable(EditorBrowsableState.Never)]
15 | public struct PooledValueTaskMethodBuilder
16 | {
17 | public override bool Equals(object? obj) => ThrowHelper.ThrowNotSupportedException();
18 | public override int GetHashCode() => ThrowHelper.ThrowNotSupportedException();
19 | public override string ToString() => nameof(PooledValueTaskMethodBuilder);
20 |
21 | private PooledValueTaskSource _source;
22 |
23 | [Browsable(false)]
24 | [EditorBrowsable(EditorBrowsableState.Never)]
25 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
26 | public static PooledValueTaskMethodBuilder Create() => default;
27 |
28 | [Browsable(false)]
29 | [EditorBrowsable(EditorBrowsableState.Never)]
30 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
31 | public void SetStateMachine(IAsyncStateMachine _) => Counters.SetStateMachine.Increment();
32 |
33 | [Browsable(false)]
34 | [EditorBrowsable(EditorBrowsableState.Never)]
35 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
36 | public void SetResult(T result)
37 | {
38 | if (_source.HasTask) _source.TrySetResult(result);
39 | else _source = new PooledValueTaskSource(result);
40 | }
41 |
42 | [Browsable(false)]
43 | [EditorBrowsable(EditorBrowsableState.Never)]
44 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
45 | public void SetException(Exception exception)
46 | {
47 | EnsureHasTask();
48 | _source.TrySetException(exception);
49 | }
50 |
51 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
52 | private void EnsureHasTask()
53 | {
54 | if (!_source.HasTask) _source = PooledValueTaskSource.Create();
55 | }
56 |
57 | [Browsable(false)]
58 | [EditorBrowsable(EditorBrowsableState.Never)]
59 | public PooledValueTask Task
60 | {
61 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
62 | get => _source.PooledTask;
63 | }
64 |
65 | [Browsable(false)]
66 | [EditorBrowsable(EditorBrowsableState.Never)]
67 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
68 | public void AwaitOnCompleted(
69 | ref TAwaiter awaiter, ref TStateMachine stateMachine)
70 | where TAwaiter : INotifyCompletion
71 | where TStateMachine : IAsyncStateMachine
72 | {
73 | EnsureHasTask();
74 | StateMachineBox.AwaitOnCompleted(ref awaiter, ref stateMachine);
75 | }
76 |
77 | [Browsable(false)]
78 | [EditorBrowsable(EditorBrowsableState.Never)]
79 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
80 | public void AwaitUnsafeOnCompleted(
81 | ref TAwaiter awaiter, ref TStateMachine stateMachine)
82 | where TAwaiter : ICriticalNotifyCompletion
83 | where TStateMachine : IAsyncStateMachine
84 | {
85 | EnsureHasTask();
86 | StateMachineBox.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
87 | }
88 |
89 | [Browsable(false)]
90 | [EditorBrowsable(EditorBrowsableState.Never)]
91 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
92 | public void Start(ref TStateMachine stateMachine)
93 | where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext();
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/PooledAwait/Pool.cs:
--------------------------------------------------------------------------------
1 | using PooledAwait.Internal;
2 | using System.Runtime.CompilerServices;
3 |
4 | namespace PooledAwait
5 | {
6 | ///