├── .editorconfig ├── .github ├── dependabot.yaml └── workflows │ ├── build-debug.yaml │ ├── build-release.yaml │ ├── stale.yaml │ └── toc.yaml ├── .gitignore ├── LICENSE ├── README.md ├── ValueTaskSupplement.sln ├── sandbox └── ConsoleApp │ ├── ConsoleApp.csproj │ └── Program.cs ├── src └── ValueTaskSupplement │ ├── ContinuationSentinel.cs │ ├── TempList.cs │ ├── ValueTaskEx.FromResult.cs │ ├── ValueTaskEx.FromTask.cs │ ├── ValueTaskEx.Lazy.cs │ ├── ValueTaskEx.Lazy_NonGenerics.cs │ ├── ValueTaskEx.WhenAll.cs │ ├── ValueTaskEx.WhenAll.tt │ ├── ValueTaskEx.WhenAll_Array.cs │ ├── ValueTaskEx.WhenAll_Array_NonGenerics.cs │ ├── ValueTaskEx.WhenAll_NonGenerics.cs │ ├── ValueTaskEx.WhenAll_NonGenerics.tt │ ├── ValueTaskEx.WhenAny.cs │ ├── ValueTaskEx.WhenAny.tt │ ├── ValueTaskEx.WhenAny_Array.cs │ ├── ValueTaskEx.WhenAny_Array_NonGenerics.cs │ ├── ValueTaskEx.WhenAny_NonGenerics.cs │ ├── ValueTaskEx.WhenAny_NonGenerics.tt │ ├── ValueTaskSupplement.csproj │ ├── ValueTaskWhenAllExtensions.cs │ ├── ValueTaskWhenAllExtensions.tt │ └── _InternalVisibleTo.cs └── tests └── ValueTaskSupplement.Tests ├── LazyTest.cs ├── LazyTest_NonGenerics.cs ├── TempListTest.cs ├── ValueTaskSupplement.Tests.csproj ├── WhenAllTest.cs ├── WhenAllTest_NonGenerics.cs ├── WhenAnyTest.cs └── WhenAnyTest_NonGenerics.cs /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_style = space 8 | indent_size = 2 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | # Visual Studio Spell checker configs (https://learn.microsoft.com/en-us/visualstudio/ide/text-spell-checker?view=vs-2022#how-to-customize-the-spell-checker) 13 | spelling_exclusion_path = ./exclusion.dic 14 | 15 | [*.cs] 16 | indent_size = 4 17 | charset = utf-8-bom 18 | end_of_line = unset 19 | 20 | # Solution files 21 | [*.{sln,slnx}] 22 | end_of_line = unset 23 | 24 | # MSBuild project files 25 | [*.{csproj,props,targets}] 26 | end_of_line = unset 27 | 28 | # Xml config files 29 | [*.{ruleset,config,nuspec,resx,runsettings,DotSettings}] 30 | end_of_line = unset 31 | 32 | [*{_AssemblyInfo.cs,.notsupported.cs}] 33 | generated_code = true 34 | 35 | # C# code style settings 36 | [*.{cs}] 37 | dotnet_diagnostic.IDE0044.severity = none # IDE0044: Make field readonly 38 | 39 | # https://stackoverflow.com/questions/79195382/how-to-disable-fading-unused-methods-in-visual-studio-2022-17-12-0 40 | dotnet_diagnostic.IDE0051.severity = none # IDE0051: Remove unused private member 41 | dotnet_diagnostic.IDE0130.severity = none # IDE0130: Namespace does not match folder structure 42 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | # ref: https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot 2 | version: 2 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" # Check for updates to GitHub Actions every week 8 | ignore: 9 | # I just want update action when major/minor version is updated. patch updates are too noisy. 10 | - dependency-name: '*' 11 | update-types: 12 | - version-update:semver-patch 13 | -------------------------------------------------------------------------------- /.github/workflows/build-debug.yaml: -------------------------------------------------------------------------------- 1 | name: Build-Debug 2 | 3 | on: 4 | push: 5 | branches: 6 | - "master" 7 | pull_request: 8 | branches: 9 | - "master" 10 | 11 | jobs: 12 | build-dotnet: 13 | permissions: 14 | contents: read 15 | runs-on: ubuntu-24.04 16 | timeout-minutes: 10 17 | steps: 18 | - uses: Cysharp/Actions/.github/actions/checkout@main 19 | - uses: Cysharp/Actions/.github/actions/setup-dotnet@main 20 | with: 21 | dotnet-version: | 22 | 6.0.x 23 | - run: dotnet build -c Debug 24 | - run: dotnet test -c Debug --no-build 25 | -------------------------------------------------------------------------------- /.github/workflows/build-release.yaml: -------------------------------------------------------------------------------- 1 | name: Build-Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | tag: 7 | description: "tag: git tag you want create. (sample 1.0.0)" 8 | required: true 9 | dry-run: 10 | description: "dry-run: true will never create release/nuget." 11 | required: true 12 | default: false 13 | type: boolean 14 | 15 | jobs: 16 | build-dotnet: 17 | permissions: 18 | contents: read 19 | runs-on: ubuntu-24.04 20 | timeout-minutes: 10 21 | steps: 22 | - uses: Cysharp/Actions/.github/actions/checkout@main 23 | - uses: Cysharp/Actions/.github/actions/setup-dotnet@main 24 | with: 25 | dotnet-version: | 26 | 6.0.x 27 | # pack nuget 28 | - run: dotnet build -c Release -p:Version=${{ inputs.tag }} 29 | - run: dotnet test -c Release --no-build -p:Version=${{ inputs.tag }} 30 | - run: dotnet pack -c Release --no-build -p:Version=${{ inputs.tag }} -o ./publish 31 | - uses: Cysharp/Actions/.github/actions/upload-artifact@main 32 | with: 33 | name: nuget 34 | path: ./publish 35 | retention-days: 1 36 | 37 | create-release: 38 | needs: [build-dotnet] 39 | permissions: 40 | contents: write 41 | uses: Cysharp/Actions/.github/workflows/create-release.yaml@main 42 | with: 43 | commit-id: '' 44 | tag: ${{ inputs.tag }} 45 | dry-run: ${{ inputs.dry-run }} 46 | nuget-push: true 47 | release-upload: false 48 | secrets: inherit 49 | -------------------------------------------------------------------------------- /.github/workflows/stale.yaml: -------------------------------------------------------------------------------- 1 | name: "Close stale issues" 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "0 0 * * *" 7 | 8 | jobs: 9 | stale: 10 | permissions: 11 | contents: read 12 | pull-requests: write 13 | issues: write 14 | uses: Cysharp/Actions/.github/workflows/stale-issue.yaml@main 15 | -------------------------------------------------------------------------------- /.github/workflows/toc.yaml: -------------------------------------------------------------------------------- 1 | name: TOC Generator 2 | 3 | on: 4 | push: 5 | paths: 6 | - 'README.md' 7 | 8 | jobs: 9 | toc: 10 | permissions: 11 | contents: write 12 | uses: Cysharp/Actions/.github/workflows/toc-generator.yaml@main 13 | with: 14 | TOC_TITLE: "## Table of Contents" 15 | secrets: inherit 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # mstest test results 6 | TestResults 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.sln.docstates 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Rr]elease/ 19 | x64/ 20 | *_i.c 21 | *_p.c 22 | *.ilk 23 | *.obj 24 | *.pch 25 | *.pdb 26 | *.pgc 27 | *.pgd 28 | *.rsp 29 | *.sbr 30 | *.tlb 31 | *.tli 32 | *.tlh 33 | *.tmp 34 | *.log 35 | *.vspscc 36 | *.vssscc 37 | .builds 38 | 39 | # Visual C++ cache files 40 | ipch/ 41 | *.aps 42 | *.ncb 43 | *.opensdf 44 | *.sdf 45 | 46 | # Visual Studio profiler 47 | *.psess 48 | *.vsp 49 | *.vspx 50 | 51 | # Guidance Automation Toolkit 52 | *.gpState 53 | 54 | # ReSharper is a .NET coding add-in 55 | _ReSharper* 56 | 57 | # NCrunch 58 | *.ncrunch* 59 | .*crunch*.local.xml 60 | 61 | # Installshield output folder 62 | [Ee]xpress 63 | 64 | # DocProject is a documentation generator add-in 65 | DocProject/buildhelp/ 66 | DocProject/Help/*.HxT 67 | DocProject/Help/*.HxC 68 | DocProject/Help/*.hhc 69 | DocProject/Help/*.hhk 70 | DocProject/Help/*.hhp 71 | DocProject/Help/Html2 72 | DocProject/Help/html 73 | 74 | # Click-Once directory 75 | publish 76 | 77 | # Publish Web Output 78 | *.Publish.xml 79 | 80 | # NuGet Packages Directory 81 | packages 82 | 83 | # Windows Azure Build Output 84 | csx 85 | *.build.csdef 86 | 87 | # Windows Store app package directory 88 | AppPackages/ 89 | 90 | # Others 91 | [Bb]in 92 | [Oo]bj 93 | sql 94 | TestResults 95 | [Tt]est[Rr]esult* 96 | *.Cache 97 | ClientBin 98 | [Ss]tyle[Cc]op.* 99 | ~$* 100 | *.dbmdl 101 | Generated_Code #added for RIA/Silverlight projects 102 | 103 | # Backup & report files from converting an old project file to a newer 104 | # Visual Studio version. Backup files are not needed, because we have git ;-) 105 | _UpgradeReport_Files/ 106 | Backup*/ 107 | UpgradeLog*.XML 108 | .vs/config/applicationhost.config 109 | .vs/restore.dg 110 | 111 | # Unity 112 | src/MasterMemory.UnityClient/bin/* 113 | src/MasterMemory.UnityClient/Library/* 114 | src/MasterMemory.UnityClient/obj/* 115 | src/MasterMemory.UnityClient/Temp/* 116 | 117 | # OTHER 118 | nuget/tools/* 119 | *.nupkg 120 | 121 | .vs 122 | 123 | # Unity 124 | Library/ 125 | Temp/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Cysharp, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GitHub Actions](https://github.com/Cysharp/ValueTaskSupplement/workflows/Build-Debug/badge.svg)](https://github.com/Cysharp/ValueTaskSupplement/actions) [![Releases](https://img.shields.io/github/release/Cysharp/ValueTaskSupplement.svg)](https://github.com/Cysharp/ValueTaskSupplement/releases) 2 | 3 | ValueTaskSupplement 4 | === 5 | 6 | `ValueTask` is a new standard of define async methods especially after being introduced `IValueTaskSource`. But it lacks utility like WhenAny, etc. ValueTaskSupplement appends supplemental methods(`WhenAny`, `WhenAll`, `Lazy`) to ValueTask and it is implemented by `IValueTaskSource` so fast and less allocation. 7 | 8 | > PM> Install-Package [ValueTaskSupplement](https://www.nuget.org/packages/ValueTaskSupplement) 9 | 10 | 11 | 12 | ## Table of Contents 13 | 14 | - [How to Use](#how-to-use) 15 | - [WhenAll](#whenall) 16 | - [WhenAny](#whenany) 17 | - [Lazy](#lazy) 18 | - [Factory](#factory) 19 | - [License](#license) 20 | 21 | 22 | 23 | How to Use 24 | --- 25 | ```csharp 26 | using ValueTaskSupplement; // namespace 27 | 28 | async ValueTask Demo() 29 | { 30 | // `ValueTaskEx` is the only types from provided this library 31 | 32 | // like this individual types 33 | ValueTask task1 = LoadAsyncA(); 34 | ValueTask task2 = LoadAsyncB(); 35 | ValueTask task3 = LoadAsyncC(); 36 | 37 | // await ValueTasks(has different type each other) with tuple 38 | var (a, b, c) = await ValueTaskEx.WhenAll(task1, task2, task3); 39 | 40 | // WhenAny with int winIndex 41 | var (winIndex, a, b, c) = await ValueTaskEx.WhenAny(task1, task2, task2); 42 | 43 | // like Timeout 44 | var (hasLeftResult, value) = await ValueTaskEx.WhenAny(task1, Task.Delay(TimeSpan.FromSeconds(1))); 45 | if (!hasLeftResult) throw new TimeoutException(); 46 | 47 | // Lazy(called factory once and delayed) 48 | AsyncLazy asyncLazy = ValueTaskEx.Lazy(async () => 9999); 49 | } 50 | ``` 51 | 52 | WhenAll 53 | --- 54 | 55 | ```csharp 56 | // Same type and return array(same as Task.WhenAll). 57 | public static ValueTask WhenAll(IEnumerable> tasks); 58 | 59 | // T0, T1, to... 60 | public static ValueTask<(T0, T1)> WhenAll(ValueTask task0, ValueTask task1); 61 | ... 62 | // T0 ~ T15 63 | public static ValueTask<(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15)> WhenAll(ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3, ValueTask task4, ValueTask task5, ValueTask task6, ValueTask task7, ValueTask task8, ValueTask task9, ValueTask task10, ValueTask task11, ValueTask task12, ValueTask task13, ValueTask task14, ValueTask task15); 64 | ``` 65 | 66 | `IEnumerable>` and `(ValueTask, ValueTask, ...)` can await directly. 67 | 68 | ```csharp 69 | using ValueTaskSupplement; 70 | 71 | // same as ValueTaskEx.WhenAll(new []{ }), ValueTaskEx.WhenAll(A, B, C) 72 | var result = await new[] { LoadAsyncA(), LoadAsyncB(), LoadAsyncC() }; 73 | var (x, y, z) = await (LoadAsyncA(), LoadAsyncB(), LoadAsyncC()); 74 | ``` 75 | 76 | WhenAny 77 | --- 78 | 79 | ```csharp 80 | // binary api is useful to await with Delay(like for check Timeout). 81 | public static ValueTask<(bool hasResultLeft, T result)> WhenAny(ValueTask left, Task right); 82 | public static ValueTask<(bool hasResultLeft, T result)> WhenAny(ValueTask left, ValueTask right); 83 | 84 | // receive sequence is like Task.WhenAny but returns `int winArgumentIndex`. 85 | public static ValueTask<(int winArgumentIndex, T result)> WhenAny(IEnumerable> tasks); 86 | 87 | // Return result of tuple methods is guaranteed only winArgumentIndex value 88 | public static ValueTask<(int winArgumentIndex, T0 result0, T1 result1)> WhenAny(ValueTask task0, ValueTask task1); 89 | ... 90 | // T0 ~ T15 91 | public static ValueTask<(int winArgumentIndex, T0 result0, T1 result1, T2 result2, T3 result3, T4 result4, T5 result5, T6 result6, T7 result7, T8 result8, T9 result9, T10 result10, T11 result11, T12 result12, T13 result13, T14 result14, T15 result15)> WhenAny(ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3, ValueTask task4, ValueTask task5, ValueTask task6, ValueTask task7, ValueTask task8, ValueTask task9, ValueTask task10, ValueTask task11, ValueTask task12, ValueTask task13, ValueTask task14, ValueTask task15); 92 | ``` 93 | 94 | Lazy 95 | --- 96 | 97 | ```csharp 98 | // AsyncLazy is similar to Lazy, it can store in field 99 | // it await directly or can convert to ValueTask easily to use WhenAll. 100 | public static AsyncLazy Lazy(Func> factory); 101 | 102 | public class AsyncLazy 103 | { 104 | public ValueTask AsValueTask(); 105 | public ValueTaskAwaiter GetAwaiter(); 106 | public static implicit operator ValueTask(AsyncLazy source); 107 | } 108 | ``` 109 | 110 | Factory 111 | --- 112 | 113 | ```csharp 114 | public static ValueTask FromResult(T result); 115 | public static ValueTask FromTask(Task result); 116 | public static ValueTask FromTask(Task result); 117 | public static ValueTask AsValueTask(this Task result); 118 | public static ValueTask AsValueTask(this Task result); 119 | ``` 120 | 121 | License 122 | --- 123 | This library is under the MIT License. 124 | -------------------------------------------------------------------------------- /ValueTaskSupplement.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29123.88 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ValueTaskSupplement", "src\ValueTaskSupplement\ValueTaskSupplement.csproj", "{D97FA156-49C8-4307-B2A6-D3C4DA94D11C}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sandbox", "sandbox", "{0780871B-9AAC-41DD-A6D7-454791E250DD}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp", "sandbox\ConsoleApp\ConsoleApp.csproj", "{9BE10864-8E46-49A4-930B-690A9D424473}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{27ACBB2F-17AB-4A41-BDD8-3403E12DDD30}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ValueTaskSupplement.Tests", "tests\ValueTaskSupplement.Tests\ValueTaskSupplement.Tests.csproj", "{39C904C9-E73C-4140-B463-A6B5087DFAAC}" 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{DDA3270C-295B-46DB-BC91-7D7AC0E96273}" 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {D97FA156-49C8-4307-B2A6-D3C4DA94D11C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {D97FA156-49C8-4307-B2A6-D3C4DA94D11C}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {D97FA156-49C8-4307-B2A6-D3C4DA94D11C}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {D97FA156-49C8-4307-B2A6-D3C4DA94D11C}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {9BE10864-8E46-49A4-930B-690A9D424473}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {9BE10864-8E46-49A4-930B-690A9D424473}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {9BE10864-8E46-49A4-930B-690A9D424473}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {9BE10864-8E46-49A4-930B-690A9D424473}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {39C904C9-E73C-4140-B463-A6B5087DFAAC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {39C904C9-E73C-4140-B463-A6B5087DFAAC}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {39C904C9-E73C-4140-B463-A6B5087DFAAC}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {39C904C9-E73C-4140-B463-A6B5087DFAAC}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(NestedProjects) = preSolution 41 | {D97FA156-49C8-4307-B2A6-D3C4DA94D11C} = {27ACBB2F-17AB-4A41-BDD8-3403E12DDD30} 42 | {9BE10864-8E46-49A4-930B-690A9D424473} = {0780871B-9AAC-41DD-A6D7-454791E250DD} 43 | {39C904C9-E73C-4140-B463-A6B5087DFAAC} = {DDA3270C-295B-46DB-BC91-7D7AC0E96273} 44 | EndGlobalSection 45 | GlobalSection(ExtensibilityGlobals) = postSolution 46 | SolutionGuid = {8468B8EC-8AF6-410B-A363-FB944322D430} 47 | EndGlobalSection 48 | EndGlobal 49 | -------------------------------------------------------------------------------- /sandbox/ConsoleApp/ConsoleApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /sandbox/ConsoleApp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using ValueTaskSupplement; 4 | 5 | namespace ConsoleApp 6 | { 7 | class Program 8 | { 9 | static async Task Main(string[] args) 10 | { 11 | var ta = Foo(); 12 | var tb = new ValueTask(100); 13 | var tc = Foo(); 14 | 15 | var tako = ValueTaskEx.WhenAll(ta, tb, tc); 16 | 17 | var (a, b, c) = await tako; 18 | 19 | } 20 | static async ValueTask Foo() 21 | { 22 | await Task.Delay(TimeSpan.FromSeconds(10)); 23 | return 10; 24 | } 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/ValueTaskSupplement/ContinuationSentinel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ValueTaskSupplement 4 | { 5 | internal static class ContinuationSentinel 6 | { 7 | public static readonly Action AvailableContinuation = _ => { }; 8 | public static readonly Action CompletedContinuation = _ => { }; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/ValueTaskSupplement/TempList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Buffers; 3 | 4 | namespace ValueTaskSupplement 5 | { 6 | internal ref struct TempList 7 | { 8 | int index; 9 | T[] array; 10 | 11 | public TempList(int initialCapacity) 12 | { 13 | this.array = ArrayPool.Shared.Rent(initialCapacity); 14 | this.index = 0; 15 | } 16 | 17 | public void Add(T value) 18 | { 19 | if (array.Length <= index) 20 | { 21 | var newArray = ArrayPool.Shared.Rent(index * 2); 22 | Array.Copy(array, newArray, index); 23 | ArrayPool.Shared.Return(array, true); 24 | array = newArray; 25 | } 26 | 27 | array[index++] = value; 28 | } 29 | 30 | public ReadOnlySpan AsSpan() 31 | { 32 | return new ReadOnlySpan(array, 0, index); 33 | } 34 | 35 | public void Dispose() 36 | { 37 | ArrayPool.Shared.Return(array, true); // clear for de-reference all. 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/ValueTaskSupplement/ValueTaskEx.FromResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.ExceptionServices; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using System.Threading.Tasks.Sources; 8 | 9 | namespace ValueTaskSupplement 10 | { 11 | public static partial class ValueTaskEx 12 | { 13 | public static ValueTask FromResult(T result) 14 | { 15 | return new ValueTask(result); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/ValueTaskSupplement/ValueTaskEx.FromTask.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | 4 | 5 | namespace ValueTaskSupplement 6 | { 7 | public static partial class ValueTaskEx 8 | { 9 | public static ValueTask FromTask(Task result) 10 | => new ValueTask(result); 11 | 12 | 13 | public static ValueTask FromTask(Task result) 14 | => new ValueTask(result); 15 | 16 | 17 | public static ValueTask AsValueTask(this Task result) 18 | => new ValueTask(result); 19 | 20 | 21 | public static ValueTask AsValueTask(this Task result) 22 | => new ValueTask(result); 23 | } 24 | } -------------------------------------------------------------------------------- /src/ValueTaskSupplement/ValueTaskEx.Lazy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using System.Threading.Tasks.Sources; 6 | 7 | namespace ValueTaskSupplement 8 | { 9 | public static partial class ValueTaskEx 10 | { 11 | public static AsyncLazy Lazy(Func> factory) 12 | { 13 | return new AsyncLazy(factory); 14 | } 15 | } 16 | 17 | public readonly struct AsyncLazy 18 | { 19 | readonly ValueTask innerTask; 20 | 21 | public AsyncLazy(Func> factory) 22 | { 23 | innerTask = new ValueTask(new AsyncLazySource(factory), 0); 24 | } 25 | 26 | public ValueTask AsValueTask() => innerTask; 27 | 28 | public ValueTaskAwaiter GetAwaiter() => innerTask.GetAwaiter(); 29 | 30 | public static implicit operator ValueTask(AsyncLazy source) 31 | { 32 | return source.AsValueTask(); 33 | } 34 | 35 | class AsyncLazySource : IValueTaskSource 36 | { 37 | static readonly ContextCallback execContextCallback = ExecutionContextCallback; 38 | static readonly SendOrPostCallback syncContextCallback = SynchronizationContextCallback; 39 | 40 | Func> factory; 41 | object syncLock; 42 | ValueTask source; 43 | bool initialized; 44 | 45 | public AsyncLazySource(Func> factory) 46 | { 47 | this.factory = factory; 48 | this.syncLock = new object(); 49 | } 50 | 51 | ValueTask GetSource() 52 | { 53 | return LazyInitializer.EnsureInitialized(ref source, ref initialized, ref syncLock, factory); 54 | } 55 | 56 | public T GetResult(short token) 57 | { 58 | return GetSource().Result; 59 | } 60 | 61 | public ValueTaskSourceStatus GetStatus(short token) 62 | { 63 | var task = GetSource(); 64 | return task.IsCompletedSuccessfully ? ValueTaskSourceStatus.Succeeded 65 | : task.IsCanceled ? ValueTaskSourceStatus.Canceled 66 | : task.IsFaulted ? ValueTaskSourceStatus.Faulted 67 | : ValueTaskSourceStatus.Pending; 68 | } 69 | 70 | public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) 71 | { 72 | var task = GetSource(); 73 | if (task.IsCompleted) 74 | { 75 | continuation(state); 76 | } 77 | else 78 | { 79 | OnCompletedSlow(task, continuation, state, flags); 80 | } 81 | } 82 | 83 | static async void OnCompletedSlow(ValueTask source, Action continuation, object? state, ValueTaskSourceOnCompletedFlags flags) 84 | { 85 | ExecutionContext? execContext = null; 86 | SynchronizationContext? syncContext = null; 87 | if ((flags & ValueTaskSourceOnCompletedFlags.FlowExecutionContext) == ValueTaskSourceOnCompletedFlags.FlowExecutionContext) 88 | { 89 | execContext = ExecutionContext.Capture(); 90 | } 91 | if ((flags & ValueTaskSourceOnCompletedFlags.UseSchedulingContext) == ValueTaskSourceOnCompletedFlags.UseSchedulingContext) 92 | { 93 | syncContext = SynchronizationContext.Current; 94 | } 95 | 96 | try 97 | { 98 | await source.ConfigureAwait(false); 99 | } 100 | catch { } 101 | 102 | if (execContext != null) 103 | { 104 | ExecutionContext.Run(execContext, execContextCallback, Tuple.Create(continuation, state, syncContext)); 105 | } 106 | else if (syncContext != null) 107 | { 108 | syncContext.Post(syncContextCallback, Tuple.Create(continuation, state, syncContext)); 109 | } 110 | else 111 | { 112 | continuation(state); 113 | } 114 | } 115 | 116 | static void ExecutionContextCallback(object state) 117 | { 118 | var t = (Tuple, object, SynchronizationContext>)state; 119 | if (t.Item3 != null) 120 | { 121 | SynchronizationContextCallback(state); 122 | } 123 | else 124 | { 125 | t.Item1.Invoke(t.Item2); 126 | } 127 | } 128 | 129 | static void SynchronizationContextCallback(object state) 130 | { 131 | var t = (Tuple, object, SynchronizationContext>)state; 132 | t.Item1.Invoke(t.Item2); 133 | } 134 | } 135 | } 136 | } -------------------------------------------------------------------------------- /src/ValueTaskSupplement/ValueTaskEx.Lazy_NonGenerics.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using System.Threading.Tasks.Sources; 6 | 7 | namespace ValueTaskSupplement 8 | { 9 | public static partial class ValueTaskEx 10 | { 11 | public static AsyncLazy Lazy(Func factory) 12 | { 13 | return new AsyncLazy(factory); 14 | } 15 | } 16 | 17 | public readonly struct AsyncLazy 18 | { 19 | readonly ValueTask innerTask; 20 | 21 | public AsyncLazy(Func factory) 22 | { 23 | innerTask = new ValueTask(new AsyncLazySource(factory), 0); 24 | } 25 | 26 | public ValueTask AsValueTask() => innerTask; 27 | 28 | public ValueTaskAwaiter GetAwaiter() => innerTask.GetAwaiter(); 29 | 30 | public static implicit operator ValueTask(AsyncLazy source) 31 | { 32 | return source.AsValueTask(); 33 | } 34 | 35 | class AsyncLazySource : IValueTaskSource 36 | { 37 | static readonly ContextCallback execContextCallback = ExecutionContextCallback; 38 | static readonly SendOrPostCallback syncContextCallback = SynchronizationContextCallback; 39 | 40 | Func factory; 41 | object syncLock; 42 | ValueTask source; 43 | bool initialized; 44 | 45 | public AsyncLazySource(Func factory) 46 | { 47 | this.factory = factory; 48 | this.syncLock = new object(); 49 | } 50 | 51 | ValueTask GetSource() 52 | { 53 | return LazyInitializer.EnsureInitialized(ref source, ref initialized, ref syncLock, factory); 54 | } 55 | 56 | public void GetResult(short token) 57 | { 58 | GetSource().GetAwaiter().GetResult(); 59 | } 60 | 61 | public ValueTaskSourceStatus GetStatus(short token) 62 | { 63 | var task = GetSource(); 64 | return task.IsCompletedSuccessfully ? ValueTaskSourceStatus.Succeeded 65 | : task.IsCanceled ? ValueTaskSourceStatus.Canceled 66 | : task.IsFaulted ? ValueTaskSourceStatus.Faulted 67 | : ValueTaskSourceStatus.Pending; 68 | } 69 | 70 | public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) 71 | { 72 | var task = GetSource(); 73 | if (task.IsCompleted) 74 | { 75 | continuation(state); 76 | } 77 | else 78 | { 79 | OnCompletedSlow(task, continuation, state, flags); 80 | } 81 | } 82 | 83 | static async void OnCompletedSlow(ValueTask source, Action continuation, object? state, ValueTaskSourceOnCompletedFlags flags) 84 | { 85 | ExecutionContext? execContext = null; 86 | SynchronizationContext? syncContext = null; 87 | if ((flags & ValueTaskSourceOnCompletedFlags.FlowExecutionContext) == ValueTaskSourceOnCompletedFlags.FlowExecutionContext) 88 | { 89 | execContext = ExecutionContext.Capture(); 90 | } 91 | if ((flags & ValueTaskSourceOnCompletedFlags.UseSchedulingContext) == ValueTaskSourceOnCompletedFlags.UseSchedulingContext) 92 | { 93 | syncContext = SynchronizationContext.Current; 94 | } 95 | 96 | try 97 | { 98 | await source.ConfigureAwait(false); 99 | } 100 | catch { } 101 | 102 | if (execContext != null) 103 | { 104 | ExecutionContext.Run(execContext, execContextCallback, Tuple.Create(continuation, state, syncContext)); 105 | } 106 | else if (syncContext != null) 107 | { 108 | syncContext.Post(syncContextCallback, Tuple.Create(continuation, state, syncContext)); 109 | } 110 | else 111 | { 112 | continuation(state); 113 | } 114 | } 115 | 116 | static void ExecutionContextCallback(object state) 117 | { 118 | var t = (Tuple, object, SynchronizationContext>)state; 119 | if (t.Item3 != null) 120 | { 121 | SynchronizationContextCallback(state); 122 | } 123 | else 124 | { 125 | t.Item1.Invoke(t.Item2); 126 | } 127 | } 128 | 129 | static void SynchronizationContextCallback(object state) 130 | { 131 | var t = (Tuple, object, SynchronizationContext>)state; 132 | t.Item1.Invoke(t.Item2); 133 | } 134 | } 135 | } 136 | } -------------------------------------------------------------------------------- /src/ValueTaskSupplement/ValueTaskEx.WhenAll.tt: -------------------------------------------------------------------------------- 1 | <#@ template debug="false" hostspecific="false" language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Linq" #> 4 | <#@ import namespace="System.Text" #> 5 | <#@ import namespace="System.Collections.Generic" #> 6 | <#@ output extension=".cs" #> 7 | using System; 8 | using System.Runtime.CompilerServices; 9 | using System.Runtime.ExceptionServices; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | using System.Threading.Tasks.Sources; 13 | 14 | namespace ValueTaskSupplement 15 | { 16 | public static partial class ValueTaskEx 17 | { 18 | <# for(var i = 1; i <= 15; i++ ) { 19 | var range = Enumerable.Range(0, i + 1); 20 | var t = string.Join(", ", range.Select(x => "T" + x)); 21 | var args = string.Join(", ", range.Select(x => $"ValueTask task{x}")); 22 | var targs = string.Join(", ", range.Select(x => $"task{x}")); 23 | var tresult = string.Join(", ", range.Select(x => $"task{x}.Result")); 24 | var completedSuccessfullyAnd = string.Join(" && ", range.Select(x => $"task{x}.IsCompletedSuccessfully")); 25 | var tfield = string.Join(", ", range.Select(x => $"t{x}")); 26 | #> 27 | public static ValueTask<(<#= t #>)> WhenAll<<#= t #>>(<#= args #>) 28 | { 29 | if (<#= completedSuccessfullyAnd #>) 30 | { 31 | return new ValueTask<(<#= t #>)>((<#= tresult #>)); 32 | } 33 | 34 | return new ValueTask<(<#= t #>)>(new WhenAllPromise<<#= t #>>(<#= targs #>), 0); 35 | } 36 | 37 | class WhenAllPromise<<#= t #>> : IValueTaskSource<(<#= t #>)> 38 | { 39 | const int ResultCount = <#= i + 1 #>; 40 | static readonly ContextCallback execContextCallback = ExecutionContextCallback; 41 | static readonly SendOrPostCallback syncContextCallback = SynchronizationContextCallback; 42 | 43 | <# for(var j = 0; j <= i; j++) { #> 44 | T<#= j #> t<#= j #> = default!; 45 | <# } #> 46 | <# for(var j = 0; j <= i; j++) { #> 47 | ValueTaskAwaiter> awaiter<#= j #>; 48 | <# } #> 49 | 50 | int completedCount = 0; 51 | ExceptionDispatchInfo? exception; 52 | Action continuation = ContinuationSentinel.AvailableContinuation; 53 | Action? invokeContinuation; 54 | object? state; 55 | SynchronizationContext? syncContext; 56 | ExecutionContext? execContext; 57 | 58 | public WhenAllPromise(<#= args #>) 59 | { 60 | <# for(var j = 0; j <= i; j++) { #> 61 | { 62 | var awaiter = task<#= j #>.GetAwaiter(); 63 | if (awaiter.IsCompleted) 64 | { 65 | try 66 | { 67 | t<#= j #> = awaiter.GetResult(); 68 | } 69 | catch (Exception ex) 70 | { 71 | exception = ExceptionDispatchInfo.Capture(ex); 72 | return; 73 | } 74 | TryInvokeContinuationWithIncrement(); 75 | } 76 | else 77 | { 78 | awaiter<#= j #> = awaiter; 79 | awaiter.UnsafeOnCompleted(ContinuationT<#= j #>); 80 | } 81 | } 82 | <# } #> 83 | } 84 | 85 | <# for(var j = 0; j <= i; j++) { #> 86 | void ContinuationT<#= j #>() 87 | { 88 | try 89 | { 90 | t<#= j #> = awaiter<#= j #>.GetResult(); 91 | } 92 | catch (Exception ex) 93 | { 94 | exception = ExceptionDispatchInfo.Capture(ex); 95 | TryInvokeContinuation(); 96 | return; 97 | } 98 | TryInvokeContinuationWithIncrement(); 99 | } 100 | 101 | <# } #> 102 | 103 | void TryInvokeContinuationWithIncrement() 104 | { 105 | if (Interlocked.Increment(ref completedCount) == ResultCount) 106 | { 107 | TryInvokeContinuation(); 108 | } 109 | } 110 | 111 | void TryInvokeContinuation() 112 | { 113 | var c = Interlocked.Exchange(ref continuation, ContinuationSentinel.CompletedContinuation); 114 | if (c != ContinuationSentinel.AvailableContinuation && c != ContinuationSentinel.CompletedContinuation) 115 | { 116 | var spinWait = new SpinWait(); 117 | while (state == null) // worst case, state is not set yet so wait. 118 | { 119 | spinWait.SpinOnce(); 120 | } 121 | 122 | if (execContext != null) 123 | { 124 | invokeContinuation = c; 125 | ExecutionContext.Run(execContext, execContextCallback, this); 126 | } 127 | else if (syncContext != null) 128 | { 129 | invokeContinuation = c; 130 | syncContext.Post(syncContextCallback, this); 131 | } 132 | else 133 | { 134 | c(state); 135 | } 136 | } 137 | } 138 | 139 | public (<#= t #>) GetResult(short token) 140 | { 141 | if (exception != null) 142 | { 143 | exception.Throw(); 144 | } 145 | return (<#= tfield #>); 146 | } 147 | 148 | public ValueTaskSourceStatus GetStatus(short token) 149 | { 150 | return (completedCount == ResultCount) ? ValueTaskSourceStatus.Succeeded 151 | : (exception != null) ? ((exception.SourceException is OperationCanceledException) ? ValueTaskSourceStatus.Canceled : ValueTaskSourceStatus.Faulted) 152 | : ValueTaskSourceStatus.Pending; 153 | } 154 | 155 | public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) 156 | { 157 | var c = Interlocked.CompareExchange(ref this.continuation, continuation, ContinuationSentinel.AvailableContinuation); 158 | if (c == ContinuationSentinel.CompletedContinuation) 159 | { 160 | continuation(state); 161 | return; 162 | } 163 | 164 | if (c != ContinuationSentinel.AvailableContinuation) 165 | { 166 | throw new InvalidOperationException("does not allow multiple await."); 167 | } 168 | 169 | if (state == null) 170 | { 171 | throw new InvalidOperationException("invalid state."); 172 | } 173 | 174 | if ((flags & ValueTaskSourceOnCompletedFlags.FlowExecutionContext) == ValueTaskSourceOnCompletedFlags.FlowExecutionContext) 175 | { 176 | execContext = ExecutionContext.Capture(); 177 | } 178 | if ((flags & ValueTaskSourceOnCompletedFlags.UseSchedulingContext) == ValueTaskSourceOnCompletedFlags.UseSchedulingContext) 179 | { 180 | syncContext = SynchronizationContext.Current; 181 | } 182 | this.state = state; 183 | 184 | if (GetStatus(token) != ValueTaskSourceStatus.Pending) 185 | { 186 | TryInvokeContinuation(); 187 | } 188 | } 189 | 190 | static void ExecutionContextCallback(object state) 191 | { 192 | var self = (WhenAllPromise<<#= t #>>)state; 193 | if (self.syncContext != null) 194 | { 195 | self.syncContext.Post(syncContextCallback, self); 196 | } 197 | else 198 | { 199 | var invokeContinuation = self.invokeContinuation!; 200 | var invokeState = self.state; 201 | self.invokeContinuation = null; 202 | self.state = null; 203 | invokeContinuation(invokeState); 204 | } 205 | } 206 | 207 | static void SynchronizationContextCallback(object state) 208 | { 209 | var self = (WhenAllPromise<<#= t #>>)state; 210 | var invokeContinuation = self.invokeContinuation!; 211 | var invokeState = self.state; 212 | self.invokeContinuation = null; 213 | self.state = null; 214 | invokeContinuation(invokeState); 215 | } 216 | } 217 | 218 | <# } #> 219 | } 220 | } -------------------------------------------------------------------------------- /src/ValueTaskSupplement/ValueTaskEx.WhenAll_Array.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.ExceptionServices; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using System.Threading.Tasks.Sources; 8 | 9 | namespace ValueTaskSupplement 10 | { 11 | public static partial class ValueTaskEx 12 | { 13 | public static ValueTask WhenAll(IEnumerable> tasks) 14 | { 15 | return new ValueTask(new WhenAllPromiseAll(tasks), 0); 16 | } 17 | 18 | class WhenAllPromiseAll : IValueTaskSource 19 | { 20 | static readonly ContextCallback execContextCallback = ExecutionContextCallback; 21 | static readonly SendOrPostCallback syncContextCallback = SynchronizationContextCallback; 22 | 23 | int completedCount = 0; 24 | ExceptionDispatchInfo? exception; 25 | Action continuation = ContinuationSentinel.AvailableContinuation; 26 | Action? invokeContinuation; 27 | object? state; 28 | SynchronizationContext? syncContext; 29 | ExecutionContext? execContext; 30 | 31 | T[] result; 32 | 33 | public WhenAllPromiseAll(IEnumerable> tasks) 34 | { 35 | if (tasks is ValueTask[] array) 36 | { 37 | result = CreateArray(array.Length); 38 | Run(array); 39 | return; 40 | } 41 | if (tasks is IReadOnlyCollection> c) 42 | { 43 | result = CreateArray(c.Count); 44 | Run(c, c.Count); 45 | return; 46 | } 47 | if (tasks is ICollection> c2) 48 | { 49 | result = CreateArray(c2.Count); 50 | Run(c2, c2.Count); 51 | return; 52 | } 53 | 54 | var list = new TempList>(99); 55 | try 56 | { 57 | foreach (var item in tasks) 58 | { 59 | list.Add(item); 60 | } 61 | 62 | var span = list.AsSpan(); 63 | result = CreateArray(span.Length); 64 | Run(span); 65 | } 66 | finally 67 | { 68 | list.Dispose(); 69 | } 70 | } 71 | 72 | T[] CreateArray(int length) 73 | { 74 | if (length == 0) return Array.Empty(); 75 | return new T[length]; 76 | } 77 | 78 | void Run(ReadOnlySpan> tasks) 79 | { 80 | var i = 0; 81 | foreach (var task in tasks) 82 | { 83 | var awaiter = task.GetAwaiter(); 84 | if (awaiter.IsCompleted) 85 | { 86 | try 87 | { 88 | result[i] = awaiter.GetResult(); 89 | } 90 | catch (Exception ex) 91 | { 92 | exception = ExceptionDispatchInfo.Capture(ex); 93 | return; 94 | } 95 | TryInvokeContinuationWithIncrement(); 96 | } 97 | else 98 | { 99 | RegisterContinuation(awaiter, i); 100 | } 101 | 102 | i++; 103 | } 104 | } 105 | 106 | void Run(IEnumerable> tasks, int _) 107 | { 108 | var i = 0; 109 | foreach (var task in tasks) 110 | { 111 | var awaiter = task.GetAwaiter(); 112 | if (awaiter.IsCompleted) 113 | { 114 | try 115 | { 116 | result[i] = awaiter.GetResult(); 117 | } 118 | catch (Exception ex) 119 | { 120 | exception = ExceptionDispatchInfo.Capture(ex); 121 | return; 122 | } 123 | TryInvokeContinuationWithIncrement(); 124 | } 125 | else 126 | { 127 | RegisterContinuation(awaiter, i); 128 | } 129 | 130 | i++; 131 | } 132 | } 133 | 134 | void RegisterContinuation(ValueTaskAwaiter awaiter, int index) 135 | { 136 | // allow capture lambda 137 | awaiter.UnsafeOnCompleted(() => 138 | { 139 | try 140 | { 141 | result[index] = awaiter.GetResult(); 142 | } 143 | catch (Exception ex) 144 | { 145 | exception = ExceptionDispatchInfo.Capture(ex); 146 | TryInvokeContinuation(); 147 | return; 148 | } 149 | TryInvokeContinuationWithIncrement(); 150 | }); 151 | } 152 | 153 | void TryInvokeContinuationWithIncrement() 154 | { 155 | if (Interlocked.Increment(ref completedCount) == result.Length) 156 | { 157 | TryInvokeContinuation(); 158 | } 159 | } 160 | 161 | void TryInvokeContinuation() 162 | { 163 | var c = Interlocked.Exchange(ref continuation, ContinuationSentinel.CompletedContinuation); 164 | if (c != ContinuationSentinel.AvailableContinuation && c != ContinuationSentinel.CompletedContinuation) 165 | { 166 | var spinWait = new SpinWait(); 167 | while (state == null) // worst case, state is not set yet so wait. 168 | { 169 | spinWait.SpinOnce(); 170 | } 171 | 172 | if (execContext != null) 173 | { 174 | invokeContinuation = c; 175 | ExecutionContext.Run(execContext, execContextCallback, this); 176 | } 177 | else if (syncContext != null) 178 | { 179 | invokeContinuation = c; 180 | syncContext.Post(syncContextCallback, this); 181 | } 182 | else 183 | { 184 | c(state); 185 | } 186 | } 187 | } 188 | 189 | public T[] GetResult(short token) 190 | { 191 | if (exception != null) 192 | { 193 | exception.Throw(); 194 | } 195 | return result; 196 | } 197 | 198 | public ValueTaskSourceStatus GetStatus(short token) 199 | { 200 | return (completedCount == result.Length) ? ValueTaskSourceStatus.Succeeded 201 | : (exception != null) ? ((exception.SourceException is OperationCanceledException) ? ValueTaskSourceStatus.Canceled : ValueTaskSourceStatus.Faulted) 202 | : ValueTaskSourceStatus.Pending; 203 | } 204 | 205 | public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) 206 | { 207 | var c = Interlocked.CompareExchange(ref this.continuation, continuation, ContinuationSentinel.AvailableContinuation); 208 | if (c == ContinuationSentinel.CompletedContinuation) 209 | { 210 | continuation(state); 211 | return; 212 | } 213 | 214 | if (c != ContinuationSentinel.AvailableContinuation) 215 | { 216 | throw new InvalidOperationException("does not allow multiple await."); 217 | } 218 | 219 | if (state == null) 220 | { 221 | throw new InvalidOperationException("invalid state."); 222 | } 223 | 224 | if ((flags & ValueTaskSourceOnCompletedFlags.FlowExecutionContext) == ValueTaskSourceOnCompletedFlags.FlowExecutionContext) 225 | { 226 | execContext = ExecutionContext.Capture(); 227 | } 228 | if ((flags & ValueTaskSourceOnCompletedFlags.UseSchedulingContext) == ValueTaskSourceOnCompletedFlags.UseSchedulingContext) 229 | { 230 | syncContext = SynchronizationContext.Current; 231 | } 232 | this.state = state; // go signal 233 | 234 | if (GetStatus(token) != ValueTaskSourceStatus.Pending) 235 | { 236 | TryInvokeContinuation(); 237 | } 238 | } 239 | 240 | static void ExecutionContextCallback(object state) 241 | { 242 | var self = (WhenAllPromiseAll)state; 243 | if (self.syncContext != null) 244 | { 245 | self.syncContext.Post(syncContextCallback, self); 246 | } 247 | else 248 | { 249 | var invokeContinuation = self.invokeContinuation!; 250 | var invokeState = self.state; 251 | self.invokeContinuation = null; 252 | self.state = null; 253 | invokeContinuation(invokeState); 254 | } 255 | } 256 | 257 | static void SynchronizationContextCallback(object state) 258 | { 259 | var self = (WhenAllPromiseAll)state; 260 | var invokeContinuation = self.invokeContinuation!; 261 | var invokeState = self.state; 262 | self.invokeContinuation = null; 263 | self.state = null; 264 | invokeContinuation(invokeState); 265 | } 266 | } 267 | } 268 | } -------------------------------------------------------------------------------- /src/ValueTaskSupplement/ValueTaskEx.WhenAll_Array_NonGenerics.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.ExceptionServices; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using System.Threading.Tasks.Sources; 8 | 9 | namespace ValueTaskSupplement 10 | { 11 | public static partial class ValueTaskEx 12 | { 13 | public static ValueTask WhenAll(IEnumerable tasks) 14 | { 15 | return new ValueTask(new WhenAllPromiseAll(tasks), 0); 16 | } 17 | 18 | class WhenAllPromiseAll : IValueTaskSource 19 | { 20 | static readonly ContextCallback execContextCallback = ExecutionContextCallback; 21 | static readonly SendOrPostCallback syncContextCallback = SynchronizationContextCallback; 22 | 23 | int taskCount = 0; 24 | int completedCount = 0; 25 | ExceptionDispatchInfo? exception; 26 | Action continuation = ContinuationSentinel.AvailableContinuation; 27 | Action? invokeContinuation; 28 | object? state; 29 | SynchronizationContext? syncContext; 30 | ExecutionContext? execContext; 31 | 32 | public WhenAllPromiseAll(IEnumerable tasks) 33 | { 34 | if (tasks is ValueTask[] array) 35 | { 36 | Run(array); 37 | return; 38 | } 39 | if (tasks is IReadOnlyCollection c) 40 | { 41 | Run(c, c.Count); 42 | return; 43 | } 44 | if (tasks is ICollection c2) 45 | { 46 | Run(c2, c2.Count); 47 | return; 48 | } 49 | 50 | var list = new TempList(99); 51 | try 52 | { 53 | foreach (var item in tasks) 54 | { 55 | list.Add(item); 56 | } 57 | 58 | Run(list.AsSpan()); 59 | } 60 | finally 61 | { 62 | list.Dispose(); 63 | } 64 | } 65 | 66 | void Run(ReadOnlySpan tasks) 67 | { 68 | taskCount = tasks.Length; 69 | 70 | var i = 0; 71 | foreach (var task in tasks) 72 | { 73 | var awaiter = task.GetAwaiter(); 74 | if (awaiter.IsCompleted) 75 | { 76 | try 77 | { 78 | awaiter.GetResult(); 79 | } 80 | catch (Exception ex) 81 | { 82 | exception = ExceptionDispatchInfo.Capture(ex); 83 | return; 84 | } 85 | TryInvokeContinuationWithIncrement(); 86 | } 87 | else 88 | { 89 | RegisterContinuation(awaiter, i); 90 | } 91 | 92 | i++; 93 | } 94 | } 95 | 96 | void Run(IEnumerable tasks, int length) 97 | { 98 | taskCount = length; 99 | 100 | var i = 0; 101 | foreach (var task in tasks) 102 | { 103 | var awaiter = task.GetAwaiter(); 104 | if (awaiter.IsCompleted) 105 | { 106 | try 107 | { 108 | awaiter.GetResult(); 109 | } 110 | catch (Exception ex) 111 | { 112 | exception = ExceptionDispatchInfo.Capture(ex); 113 | return; 114 | } 115 | TryInvokeContinuationWithIncrement(); 116 | } 117 | else 118 | { 119 | RegisterContinuation(awaiter, i); 120 | } 121 | 122 | i++; 123 | } 124 | } 125 | 126 | void RegisterContinuation(ValueTaskAwaiter awaiter, int index) 127 | { 128 | awaiter.UnsafeOnCompleted(() => 129 | { 130 | try 131 | { 132 | awaiter.GetResult(); 133 | } 134 | catch (Exception ex) 135 | { 136 | exception = ExceptionDispatchInfo.Capture(ex); 137 | TryInvokeContinuation(); 138 | return; 139 | } 140 | TryInvokeContinuationWithIncrement(); 141 | }); 142 | } 143 | 144 | void TryInvokeContinuationWithIncrement() 145 | { 146 | if (Interlocked.Increment(ref completedCount) == taskCount) 147 | { 148 | TryInvokeContinuation(); 149 | } 150 | } 151 | 152 | void TryInvokeContinuation() 153 | { 154 | var c = Interlocked.Exchange(ref continuation, ContinuationSentinel.CompletedContinuation); 155 | if (c != ContinuationSentinel.AvailableContinuation && c != ContinuationSentinel.CompletedContinuation) 156 | { 157 | var spinWait = new SpinWait(); 158 | while (state == null) // worst case, state is not set yet so wait. 159 | { 160 | spinWait.SpinOnce(); 161 | } 162 | 163 | if (execContext != null) 164 | { 165 | invokeContinuation = c; 166 | ExecutionContext.Run(execContext, execContextCallback, this); 167 | } 168 | else if (syncContext != null) 169 | { 170 | invokeContinuation = c; 171 | syncContext.Post(syncContextCallback, this); 172 | } 173 | else 174 | { 175 | c(state); 176 | } 177 | } 178 | } 179 | 180 | public void GetResult(short token) 181 | { 182 | if (exception != null) 183 | { 184 | exception.Throw(); 185 | } 186 | } 187 | 188 | public ValueTaskSourceStatus GetStatus(short token) 189 | { 190 | return (completedCount == taskCount) ? ValueTaskSourceStatus.Succeeded 191 | : (exception != null) ? ((exception.SourceException is OperationCanceledException) ? ValueTaskSourceStatus.Canceled : ValueTaskSourceStatus.Faulted) 192 | : ValueTaskSourceStatus.Pending; 193 | } 194 | 195 | public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) 196 | { 197 | var c = Interlocked.CompareExchange(ref this.continuation, continuation, ContinuationSentinel.AvailableContinuation); 198 | if (c == ContinuationSentinel.CompletedContinuation) 199 | { 200 | continuation(state); 201 | return; 202 | } 203 | 204 | if (c != ContinuationSentinel.AvailableContinuation) 205 | { 206 | throw new InvalidOperationException("does not allow multiple await."); 207 | } 208 | 209 | if (state == null) 210 | { 211 | throw new InvalidOperationException("invalid state."); 212 | } 213 | 214 | if ((flags & ValueTaskSourceOnCompletedFlags.FlowExecutionContext) == ValueTaskSourceOnCompletedFlags.FlowExecutionContext) 215 | { 216 | execContext = ExecutionContext.Capture(); 217 | } 218 | if ((flags & ValueTaskSourceOnCompletedFlags.UseSchedulingContext) == ValueTaskSourceOnCompletedFlags.UseSchedulingContext) 219 | { 220 | syncContext = SynchronizationContext.Current; 221 | } 222 | this.state = state; 223 | 224 | if (GetStatus(token) != ValueTaskSourceStatus.Pending) 225 | { 226 | TryInvokeContinuation(); 227 | } 228 | } 229 | 230 | static void ExecutionContextCallback(object state) 231 | { 232 | var self = (WhenAllPromiseAll)state; 233 | if (self.syncContext != null) 234 | { 235 | self.syncContext.Post(syncContextCallback, self); 236 | } 237 | else 238 | { 239 | var invokeContinuation = self.invokeContinuation!; 240 | var invokeState = self.state; 241 | self.invokeContinuation = null; 242 | self.state = null; 243 | invokeContinuation(invokeState); 244 | } 245 | } 246 | 247 | static void SynchronizationContextCallback(object state) 248 | { 249 | var self = (WhenAllPromiseAll)state; 250 | var invokeContinuation = self.invokeContinuation!; 251 | var invokeState = self.state; 252 | self.invokeContinuation = null; 253 | self.state = null; 254 | invokeContinuation(invokeState); 255 | } 256 | } 257 | 258 | } 259 | } -------------------------------------------------------------------------------- /src/ValueTaskSupplement/ValueTaskEx.WhenAll_NonGenerics.tt: -------------------------------------------------------------------------------- 1 | <#@ template debug="false" hostspecific="false" language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Linq" #> 4 | <#@ import namespace="System.Text" #> 5 | <#@ import namespace="System.Collections.Generic" #> 6 | <#@ output extension=".cs" #> 7 | using System; 8 | using System.Runtime.CompilerServices; 9 | using System.Runtime.ExceptionServices; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | using System.Threading.Tasks.Sources; 13 | 14 | namespace ValueTaskSupplement 15 | { 16 | public static partial class ValueTaskEx 17 | { 18 | <# for(var i = 1; i <= 15; i++ ) { 19 | var range = Enumerable.Range(0, i + 1); 20 | var args = string.Join(", ", range.Select(x => $"ValueTask task{x}")); 21 | var targs = string.Join(", ", range.Select(x => $"task{x}")); 22 | var tresult = string.Join(", ", range.Select(x => $"task{x}.Result")); 23 | var completedSuccessfullyAnd = string.Join(" && ", range.Select(x => $"task{x}.IsCompletedSuccessfully")); 24 | var tfield = string.Join(", ", range.Select(x => $"t{x}")); 25 | #> 26 | public static ValueTask WhenAll(<#= args #>) 27 | { 28 | if (<#= completedSuccessfullyAnd #>) 29 | { 30 | return default; 31 | } 32 | 33 | return new ValueTask(new WhenAllPromise<#= i + 1 #>(<#= targs #>), 0); 34 | } 35 | 36 | class WhenAllPromise<#= i + 1 #> : IValueTaskSource 37 | { 38 | const int ResultCount = <#= i + 1 #>; 39 | static readonly ContextCallback execContextCallback = ExecutionContextCallback; 40 | static readonly SendOrPostCallback syncContextCallback = SynchronizationContextCallback; 41 | 42 | <# for(var j = 0; j <= i; j++) { #> 43 | ValueTaskAwaiter awaiter<#= j #>; 44 | <# } #> 45 | 46 | int completedCount = 0; 47 | ExceptionDispatchInfo? exception; 48 | Action continuation = ContinuationSentinel.AvailableContinuation; 49 | Action? invokeContinuation; 50 | object? state; 51 | SynchronizationContext? syncContext; 52 | ExecutionContext? execContext; 53 | 54 | public WhenAllPromise<#= i + 1 #>(<#= args #>) 55 | { 56 | <# for(var j = 0; j <= i; j++) { #> 57 | { 58 | var awaiter = task<#= j #>.GetAwaiter(); 59 | if (awaiter.IsCompleted) 60 | { 61 | try 62 | { 63 | awaiter.GetResult(); 64 | } 65 | catch (Exception ex) 66 | { 67 | exception = ExceptionDispatchInfo.Capture(ex); 68 | return; 69 | } 70 | TryInvokeContinuationWithIncrement(); 71 | } 72 | else 73 | { 74 | awaiter<#= j #> = awaiter; 75 | awaiter.UnsafeOnCompleted(Continuation<#= j #>); 76 | } 77 | } 78 | <# } #> 79 | } 80 | 81 | <# for(var j = 0; j <= i; j++) { #> 82 | void Continuation<#= j #>() 83 | { 84 | try 85 | { 86 | awaiter<#= j #>.GetResult(); 87 | } 88 | catch (Exception ex) 89 | { 90 | exception = ExceptionDispatchInfo.Capture(ex); 91 | TryInvokeContinuation(); 92 | return; 93 | } 94 | TryInvokeContinuationWithIncrement(); 95 | } 96 | 97 | <# } #> 98 | 99 | void TryInvokeContinuationWithIncrement() 100 | { 101 | if (Interlocked.Increment(ref completedCount) == ResultCount) 102 | { 103 | TryInvokeContinuation(); 104 | } 105 | } 106 | 107 | void TryInvokeContinuation() 108 | { 109 | var c = Interlocked.Exchange(ref continuation, ContinuationSentinel.CompletedContinuation); 110 | if (c != ContinuationSentinel.AvailableContinuation && c != ContinuationSentinel.CompletedContinuation) 111 | { 112 | var spinWait = new SpinWait(); 113 | while (state == null) // worst case, state is not set yet so wait. 114 | { 115 | spinWait.SpinOnce(); 116 | } 117 | 118 | if (execContext != null) 119 | { 120 | invokeContinuation = c; 121 | ExecutionContext.Run(execContext, execContextCallback, this); 122 | } 123 | else if (syncContext != null) 124 | { 125 | invokeContinuation = c; 126 | syncContext.Post(syncContextCallback, this); 127 | } 128 | else 129 | { 130 | c(state); 131 | } 132 | } 133 | } 134 | 135 | public void GetResult(short token) 136 | { 137 | if (exception != null) 138 | { 139 | exception.Throw(); 140 | } 141 | } 142 | 143 | public ValueTaskSourceStatus GetStatus(short token) 144 | { 145 | return (completedCount == ResultCount) ? ValueTaskSourceStatus.Succeeded 146 | : (exception != null) ? ((exception.SourceException is OperationCanceledException) ? ValueTaskSourceStatus.Canceled : ValueTaskSourceStatus.Faulted) 147 | : ValueTaskSourceStatus.Pending; 148 | } 149 | 150 | public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) 151 | { 152 | var c = Interlocked.CompareExchange(ref this.continuation, continuation, ContinuationSentinel.AvailableContinuation); 153 | if (c == ContinuationSentinel.CompletedContinuation) 154 | { 155 | continuation(state); 156 | return; 157 | } 158 | 159 | if (c != ContinuationSentinel.AvailableContinuation) 160 | { 161 | throw new InvalidOperationException("does not allow multiple await."); 162 | } 163 | 164 | if (state == null) 165 | { 166 | throw new InvalidOperationException("invalid state."); 167 | } 168 | 169 | if ((flags & ValueTaskSourceOnCompletedFlags.FlowExecutionContext) == ValueTaskSourceOnCompletedFlags.FlowExecutionContext) 170 | { 171 | execContext = ExecutionContext.Capture(); 172 | } 173 | if ((flags & ValueTaskSourceOnCompletedFlags.UseSchedulingContext) == ValueTaskSourceOnCompletedFlags.UseSchedulingContext) 174 | { 175 | syncContext = SynchronizationContext.Current; 176 | } 177 | this.state = state; 178 | 179 | if (GetStatus(token) != ValueTaskSourceStatus.Pending) 180 | { 181 | TryInvokeContinuation(); 182 | } 183 | } 184 | 185 | static void ExecutionContextCallback(object state) 186 | { 187 | var self = (WhenAllPromise<#= i + 1 #>)state; 188 | if (self.syncContext != null) 189 | { 190 | self.syncContext.Post(syncContextCallback, self); 191 | } 192 | else 193 | { 194 | var invokeContinuation = self.invokeContinuation!; 195 | var invokeState = self.state; 196 | self.invokeContinuation = null; 197 | self.state = null; 198 | invokeContinuation(invokeState); 199 | } 200 | } 201 | 202 | static void SynchronizationContextCallback(object state) 203 | { 204 | var self = (WhenAllPromise<#= i + 1 #>)state; 205 | var invokeContinuation = self.invokeContinuation!; 206 | var invokeState = self.state; 207 | self.invokeContinuation = null; 208 | self.state = null; 209 | invokeContinuation(invokeState); 210 | } 211 | } 212 | 213 | <# } #> 214 | } 215 | } -------------------------------------------------------------------------------- /src/ValueTaskSupplement/ValueTaskEx.WhenAny.tt: -------------------------------------------------------------------------------- 1 | <#@ template debug="false" hostspecific="false" language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Linq" #> 4 | <#@ import namespace="System.Text" #> 5 | <#@ import namespace="System.Collections.Generic" #> 6 | <#@ output extension=".cs" #> 7 | using System; 8 | using System.Runtime.CompilerServices; 9 | using System.Runtime.ExceptionServices; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | using System.Threading.Tasks.Sources; 13 | 14 | namespace ValueTaskSupplement 15 | { 16 | public static partial class ValueTaskEx 17 | { 18 | <# for(var i = 1; i <= 15; i++ ) { 19 | var range = Enumerable.Range(0, i + 1); 20 | var t = string.Join(", ", range.Select(x => "T" + x)); 21 | var ttuple = string.Join(", ", range.Select(x => $"T{x} result{x}")); 22 | var args = string.Join(", ", range.Select(x => $"ValueTask task{x}")); 23 | var targs = string.Join(", ", range.Select(x => $"task{x}")); 24 | var tresultTuple = string.Join(", ", range.Select(x => $"t{x}")); 25 | #> 26 | public static ValueTask<(int winArgumentIndex, <#= ttuple #>)> WhenAny<<#= t #>>(<#= args #>) 27 | { 28 | return new ValueTask<(int winArgumentIndex, <#= ttuple #>)>(new WhenAnyPromise<<#= t #>>(<#= targs #>), 0); 29 | } 30 | 31 | class WhenAnyPromise<<#= t #>> : IValueTaskSource<(int winArgumentIndex, <#= ttuple #>)> 32 | { 33 | static readonly ContextCallback execContextCallback = ExecutionContextCallback; 34 | static readonly SendOrPostCallback syncContextCallback = SynchronizationContextCallback; 35 | 36 | <# for(var j = 0; j <= i; j++) { #> 37 | T<#= j #> t<#= j #> = default!; 38 | <# } #> 39 | <# for(var j = 0; j <= i; j++) { #> 40 | ValueTaskAwaiter> awaiter<#= j #>; 41 | <# } #> 42 | 43 | int completedCount = 0; 44 | int winArgumentIndex = -1; 45 | ExceptionDispatchInfo? exception; 46 | Action continuation = ContinuationSentinel.AvailableContinuation; 47 | Action? invokeContinuation; 48 | object? state; 49 | SynchronizationContext? syncContext; 50 | ExecutionContext? execContext; 51 | 52 | public WhenAnyPromise(<#= args #>) 53 | { 54 | <# for(var j = 0; j <= i; j++) { #> 55 | { 56 | var awaiter = task<#= j #>.GetAwaiter(); 57 | if (awaiter.IsCompleted) 58 | { 59 | try 60 | { 61 | t<#= j #> = awaiter.GetResult(); 62 | TryInvokeContinuationWithIncrement(<#= j #>); 63 | return; 64 | } 65 | catch (Exception ex) 66 | { 67 | exception = ExceptionDispatchInfo.Capture(ex); 68 | return; 69 | } 70 | } 71 | else 72 | { 73 | awaiter<#= j #> = awaiter; 74 | awaiter.UnsafeOnCompleted(ContinuationT<#= j #>); 75 | } 76 | } 77 | <# } #> 78 | } 79 | 80 | <# for(var j = 0; j <= i; j++) { #> 81 | void ContinuationT<#= j #>() 82 | { 83 | try 84 | { 85 | t<#= j #> = awaiter<#= j #>.GetResult(); 86 | } 87 | catch (Exception ex) 88 | { 89 | exception = ExceptionDispatchInfo.Capture(ex); 90 | TryInvokeContinuation(); 91 | return; 92 | } 93 | TryInvokeContinuationWithIncrement(<#= j #>); 94 | } 95 | 96 | <# } #> 97 | 98 | void TryInvokeContinuationWithIncrement(int index) 99 | { 100 | if (Interlocked.Increment(ref completedCount) == 1) 101 | { 102 | Volatile.Write(ref winArgumentIndex, index); 103 | TryInvokeContinuation(); 104 | } 105 | } 106 | 107 | void TryInvokeContinuation() 108 | { 109 | var c = Interlocked.Exchange(ref continuation, ContinuationSentinel.CompletedContinuation); 110 | if (c != ContinuationSentinel.AvailableContinuation && c != ContinuationSentinel.CompletedContinuation) 111 | { 112 | var spinWait = new SpinWait(); 113 | while (state == null) // worst case, state is not set yet so wait. 114 | { 115 | spinWait.SpinOnce(); 116 | } 117 | 118 | if (execContext != null) 119 | { 120 | invokeContinuation = c; 121 | ExecutionContext.Run(execContext, execContextCallback, this); 122 | } 123 | else if (syncContext != null) 124 | { 125 | invokeContinuation = c; 126 | syncContext.Post(syncContextCallback, this); 127 | } 128 | else 129 | { 130 | c(state); 131 | } 132 | } 133 | } 134 | 135 | public (int winArgumentIndex, <#= ttuple #>) GetResult(short token) 136 | { 137 | if (exception != null) 138 | { 139 | exception.Throw(); 140 | } 141 | var i = this.winArgumentIndex; 142 | return (winArgumentIndex, <#= tresultTuple #>); 143 | } 144 | 145 | public ValueTaskSourceStatus GetStatus(short token) 146 | { 147 | return (Volatile.Read(ref winArgumentIndex) != -1) ? ValueTaskSourceStatus.Succeeded 148 | : (exception != null) ? ((exception.SourceException is OperationCanceledException) ? ValueTaskSourceStatus.Canceled : ValueTaskSourceStatus.Faulted) 149 | : ValueTaskSourceStatus.Pending; 150 | } 151 | 152 | public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) 153 | { 154 | var c = Interlocked.CompareExchange(ref this.continuation, continuation, ContinuationSentinel.AvailableContinuation); 155 | if (c == ContinuationSentinel.CompletedContinuation) 156 | { 157 | continuation(state); 158 | return; 159 | } 160 | 161 | if (c != ContinuationSentinel.AvailableContinuation) 162 | { 163 | throw new InvalidOperationException("does not allow multiple await."); 164 | } 165 | 166 | if (state == null) 167 | { 168 | throw new InvalidOperationException("invalid state."); 169 | } 170 | 171 | if ((flags & ValueTaskSourceOnCompletedFlags.FlowExecutionContext) == ValueTaskSourceOnCompletedFlags.FlowExecutionContext) 172 | { 173 | execContext = ExecutionContext.Capture(); 174 | } 175 | if ((flags & ValueTaskSourceOnCompletedFlags.UseSchedulingContext) == ValueTaskSourceOnCompletedFlags.UseSchedulingContext) 176 | { 177 | syncContext = SynchronizationContext.Current; 178 | } 179 | this.state = state; 180 | 181 | if (GetStatus(token) != ValueTaskSourceStatus.Pending) 182 | { 183 | TryInvokeContinuation(); 184 | } 185 | } 186 | 187 | static void ExecutionContextCallback(object state) 188 | { 189 | var self = (WhenAnyPromise<<#= t #>>)state; 190 | if (self.syncContext != null) 191 | { 192 | self.syncContext.Post(syncContextCallback, self); 193 | } 194 | else 195 | { 196 | var invokeContinuation = self.invokeContinuation!; 197 | var invokeState = self.state; 198 | self.invokeContinuation = null; 199 | self.state = null; 200 | invokeContinuation(invokeState); 201 | } 202 | } 203 | 204 | static void SynchronizationContextCallback(object state) 205 | { 206 | var self = (WhenAnyPromise<<#= t #>>)state; 207 | var invokeContinuation = self.invokeContinuation!; 208 | var invokeState = self.state; 209 | self.invokeContinuation = null; 210 | self.state = null; 211 | invokeContinuation(invokeState); 212 | } 213 | } 214 | 215 | <# } #> 216 | } 217 | } -------------------------------------------------------------------------------- /src/ValueTaskSupplement/ValueTaskEx.WhenAny_Array.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.ExceptionServices; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using System.Threading.Tasks.Sources; 8 | 9 | namespace ValueTaskSupplement 10 | { 11 | public static partial class ValueTaskEx 12 | { 13 | public static ValueTask<(bool hasResultLeft, T result)> WhenAny(ValueTask left, Task right) 14 | { 15 | return new ValueTask<(bool hasResultLeft, T result)>(new WhenAnyPromiseBinary(left, new ValueTask(right)), 0); 16 | } 17 | 18 | public static ValueTask<(bool hasResultLeft, T result)> WhenAny(ValueTask left, ValueTask right) 19 | { 20 | return new ValueTask<(bool hasResultLeft, T result)>(new WhenAnyPromiseBinary(left, right), 0); 21 | } 22 | 23 | public static ValueTask<(int winArgumentIndex, T result)> WhenAny(IEnumerable> tasks) 24 | { 25 | return new ValueTask<(int, T)>(new WhenAnyPromiseAll(tasks), 0); 26 | } 27 | 28 | class WhenAnyPromiseBinary : IValueTaskSource<(bool hasResultLeft, T result)> 29 | { 30 | static readonly ContextCallback execContextCallback = ExecutionContextCallback; 31 | static readonly SendOrPostCallback syncContextCallback = SynchronizationContextCallback; 32 | 33 | int completedCount = 0; 34 | ExceptionDispatchInfo? exception; 35 | Action continuation = ContinuationSentinel.AvailableContinuation; 36 | Action? invokeContinuation; 37 | object? state; 38 | SynchronizationContext? syncContext; 39 | ExecutionContext? execContext; 40 | 41 | T result = default!; 42 | int winArgumentIndex = -1; 43 | 44 | public WhenAnyPromiseBinary(ValueTask left, ValueTask right) 45 | { 46 | { 47 | var awaiter = left.GetAwaiter(); 48 | if (awaiter.IsCompleted) 49 | { 50 | try 51 | { 52 | var taskResult = awaiter.GetResult(); 53 | TryInvokeContinuationWithResult(taskResult, 0); 54 | return; 55 | } 56 | catch (Exception ex) 57 | { 58 | exception = ExceptionDispatchInfo.Capture(ex); 59 | return; 60 | } 61 | } 62 | else 63 | { 64 | RegisterContinuation(awaiter, 0); 65 | } 66 | } 67 | { 68 | var awaiter = right.GetAwaiter(); 69 | if (awaiter.IsCompleted) 70 | { 71 | try 72 | { 73 | awaiter.GetResult(); 74 | TryInvokeContinuationWithResult(default!, 1); 75 | return; 76 | } 77 | catch (Exception ex) 78 | { 79 | exception = ExceptionDispatchInfo.Capture(ex); 80 | return; 81 | } 82 | } 83 | else 84 | { 85 | RegisterContinuation(awaiter, 1); 86 | } 87 | } 88 | } 89 | 90 | void RegisterContinuation(ValueTaskAwaiter awaiter, int index) 91 | { 92 | awaiter.UnsafeOnCompleted(() => 93 | { 94 | try 95 | { 96 | awaiter.GetResult(); 97 | TryInvokeContinuationWithResult(default!, index); 98 | return; 99 | } 100 | catch (Exception ex) 101 | { 102 | exception = ExceptionDispatchInfo.Capture(ex); 103 | TryInvokeContinuation(); 104 | return; 105 | } 106 | }); 107 | } 108 | 109 | void RegisterContinuation(ValueTaskAwaiter awaiter, int index) 110 | { 111 | awaiter.UnsafeOnCompleted(() => 112 | { 113 | try 114 | { 115 | var taskResult = awaiter.GetResult(); 116 | TryInvokeContinuationWithResult(taskResult, index); 117 | return; 118 | } 119 | catch (Exception ex) 120 | { 121 | exception = ExceptionDispatchInfo.Capture(ex); 122 | TryInvokeContinuation(); 123 | return; 124 | } 125 | }); 126 | } 127 | 128 | void TryInvokeContinuationWithResult(T result, int winIndex) 129 | { 130 | if (Interlocked.Increment(ref completedCount) == 1) 131 | { 132 | this.result = result; 133 | Volatile.Write(ref winArgumentIndex, winIndex); 134 | TryInvokeContinuation(); 135 | } 136 | } 137 | 138 | void TryInvokeContinuation() 139 | { 140 | var c = Interlocked.Exchange(ref continuation, ContinuationSentinel.CompletedContinuation); 141 | if (c != ContinuationSentinel.AvailableContinuation && c != ContinuationSentinel.CompletedContinuation) 142 | { 143 | var spinWait = new SpinWait(); 144 | while (state == null) // worst case, state is not set yet so wait. 145 | { 146 | spinWait.SpinOnce(); 147 | } 148 | 149 | if (execContext != null) 150 | { 151 | invokeContinuation = c; 152 | ExecutionContext.Run(execContext, execContextCallback, this); 153 | } 154 | else if (syncContext != null) 155 | { 156 | invokeContinuation = c; 157 | syncContext.Post(syncContextCallback, this); 158 | } 159 | else 160 | { 161 | c(state); 162 | } 163 | } 164 | } 165 | 166 | public (bool, T) GetResult(short token) 167 | { 168 | if (exception != null) 169 | { 170 | exception.Throw(); 171 | } 172 | return (winArgumentIndex == 0, result); 173 | } 174 | 175 | public ValueTaskSourceStatus GetStatus(short token) 176 | { 177 | return (Volatile.Read(ref winArgumentIndex) != -1) ? ValueTaskSourceStatus.Succeeded 178 | : (exception != null) ? ((exception.SourceException is OperationCanceledException) ? ValueTaskSourceStatus.Canceled : ValueTaskSourceStatus.Faulted) 179 | : ValueTaskSourceStatus.Pending; 180 | } 181 | 182 | public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) 183 | { 184 | var c = Interlocked.CompareExchange(ref this.continuation, continuation, ContinuationSentinel.AvailableContinuation); 185 | if (c == ContinuationSentinel.CompletedContinuation) 186 | { 187 | continuation(state); 188 | return; 189 | } 190 | 191 | if (c != ContinuationSentinel.AvailableContinuation) 192 | { 193 | throw new InvalidOperationException("does not allow multiple await."); 194 | } 195 | 196 | if (state == null) 197 | { 198 | throw new InvalidOperationException("invalid state."); 199 | } 200 | 201 | if ((flags & ValueTaskSourceOnCompletedFlags.FlowExecutionContext) == ValueTaskSourceOnCompletedFlags.FlowExecutionContext) 202 | { 203 | execContext = ExecutionContext.Capture(); 204 | } 205 | if ((flags & ValueTaskSourceOnCompletedFlags.UseSchedulingContext) == ValueTaskSourceOnCompletedFlags.UseSchedulingContext) 206 | { 207 | syncContext = SynchronizationContext.Current; 208 | } 209 | this.state = state; 210 | 211 | if (GetStatus(token) != ValueTaskSourceStatus.Pending) 212 | { 213 | TryInvokeContinuation(); 214 | } 215 | } 216 | 217 | static void ExecutionContextCallback(object state) 218 | { 219 | var self = (WhenAnyPromiseBinary)state; 220 | if (self.syncContext != null) 221 | { 222 | self.syncContext.Post(syncContextCallback, self); 223 | } 224 | else 225 | { 226 | var invokeContinuation = self.invokeContinuation!; 227 | var invokeState = self.state; 228 | self.invokeContinuation = null; 229 | self.state = null; 230 | invokeContinuation(invokeState); 231 | } 232 | } 233 | 234 | static void SynchronizationContextCallback(object state) 235 | { 236 | var self = (WhenAnyPromiseBinary)state; 237 | var invokeContinuation = self.invokeContinuation!; 238 | var invokeState = self.state; 239 | self.invokeContinuation = null; 240 | self.state = null; 241 | invokeContinuation(invokeState); 242 | } 243 | } 244 | 245 | class WhenAnyPromiseAll : IValueTaskSource<(int winArgumentIndex, T result)> 246 | { 247 | static readonly ContextCallback execContextCallback = ExecutionContextCallback; 248 | static readonly SendOrPostCallback syncContextCallback = SynchronizationContextCallback; 249 | 250 | int completedCount = 0; 251 | ExceptionDispatchInfo? exception; 252 | Action continuation = ContinuationSentinel.AvailableContinuation; 253 | Action? invokeContinuation; 254 | object? state; 255 | SynchronizationContext? syncContext; 256 | ExecutionContext? execContext; 257 | 258 | T result = default!; 259 | int winArgumentIndex = -1; 260 | 261 | public WhenAnyPromiseAll(IEnumerable> tasks) 262 | { 263 | var i = 0; 264 | foreach (var task in tasks) 265 | { 266 | var awaiter = task.GetAwaiter(); 267 | if (awaiter.IsCompleted) 268 | { 269 | try 270 | { 271 | var taskResult = awaiter.GetResult(); 272 | TryInvokeContinuationWithResult(taskResult, i); 273 | return; 274 | } 275 | catch (Exception ex) 276 | { 277 | exception = ExceptionDispatchInfo.Capture(ex); 278 | return; 279 | } 280 | } 281 | else 282 | { 283 | RegisterContinuation(awaiter, i); 284 | } 285 | 286 | i++; 287 | } 288 | } 289 | 290 | void RegisterContinuation(ValueTaskAwaiter awaiter, int index) 291 | { 292 | awaiter.UnsafeOnCompleted(() => 293 | { 294 | try 295 | { 296 | var taskResult = awaiter.GetResult(); 297 | TryInvokeContinuationWithResult(taskResult, index); 298 | return; 299 | } 300 | catch (Exception ex) 301 | { 302 | exception = ExceptionDispatchInfo.Capture(ex); 303 | TryInvokeContinuation(); 304 | return; 305 | } 306 | }); 307 | } 308 | 309 | void TryInvokeContinuationWithResult(T result, int winIndex) 310 | { 311 | if (Interlocked.Increment(ref completedCount) == 1) 312 | { 313 | this.result = result; 314 | Volatile.Write(ref winArgumentIndex, winIndex); 315 | TryInvokeContinuation(); 316 | } 317 | } 318 | 319 | void TryInvokeContinuation() 320 | { 321 | var c = Interlocked.Exchange(ref continuation, ContinuationSentinel.CompletedContinuation); 322 | if (c != ContinuationSentinel.AvailableContinuation && c != ContinuationSentinel.CompletedContinuation) 323 | { 324 | var spinWait = new SpinWait(); 325 | while (state == null) // worst case, state is not set yet so wait. 326 | { 327 | spinWait.SpinOnce(); 328 | } 329 | 330 | if (execContext != null) 331 | { 332 | invokeContinuation = c; 333 | ExecutionContext.Run(execContext, execContextCallback, this); 334 | } 335 | else if (syncContext != null) 336 | { 337 | invokeContinuation = c; 338 | syncContext.Post(syncContextCallback, this); 339 | } 340 | else 341 | { 342 | c(state); 343 | } 344 | } 345 | } 346 | 347 | public (int, T) GetResult(short token) 348 | { 349 | if (exception != null) 350 | { 351 | exception.Throw(); 352 | } 353 | return (winArgumentIndex, result); 354 | } 355 | 356 | public ValueTaskSourceStatus GetStatus(short token) 357 | { 358 | return (Volatile.Read(ref winArgumentIndex) != -1) ? ValueTaskSourceStatus.Succeeded 359 | : (exception != null) ? ((exception.SourceException is OperationCanceledException) ? ValueTaskSourceStatus.Canceled : ValueTaskSourceStatus.Faulted) 360 | : ValueTaskSourceStatus.Pending; 361 | } 362 | 363 | public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) 364 | { 365 | var c = Interlocked.CompareExchange(ref this.continuation, continuation, ContinuationSentinel.AvailableContinuation); 366 | if (c == ContinuationSentinel.CompletedContinuation) 367 | { 368 | continuation(state); 369 | return; 370 | } 371 | 372 | if (c != ContinuationSentinel.AvailableContinuation) 373 | { 374 | throw new InvalidOperationException("does not allow multiple await."); 375 | } 376 | 377 | if (state == null) 378 | { 379 | throw new InvalidOperationException("invalid state."); 380 | } 381 | 382 | if ((flags & ValueTaskSourceOnCompletedFlags.FlowExecutionContext) == ValueTaskSourceOnCompletedFlags.FlowExecutionContext) 383 | { 384 | execContext = ExecutionContext.Capture(); 385 | } 386 | if ((flags & ValueTaskSourceOnCompletedFlags.UseSchedulingContext) == ValueTaskSourceOnCompletedFlags.UseSchedulingContext) 387 | { 388 | syncContext = SynchronizationContext.Current; 389 | } 390 | this.state = state; 391 | 392 | if (GetStatus(token) != ValueTaskSourceStatus.Pending) 393 | { 394 | TryInvokeContinuation(); 395 | } 396 | } 397 | 398 | static void ExecutionContextCallback(object state) 399 | { 400 | var self = (WhenAnyPromiseAll)state; 401 | if (self.syncContext != null) 402 | { 403 | self.syncContext.Post(syncContextCallback, self); 404 | } 405 | else 406 | { 407 | var invokeContinuation = self.invokeContinuation!; 408 | var invokeState = self.state; 409 | self.invokeContinuation = null; 410 | self.state = null; 411 | invokeContinuation(invokeState); 412 | } 413 | } 414 | 415 | static void SynchronizationContextCallback(object state) 416 | { 417 | var self = (WhenAnyPromiseAll)state; 418 | var invokeContinuation = self.invokeContinuation!; 419 | var invokeState = self.state; 420 | self.invokeContinuation = null; 421 | self.state = null; 422 | invokeContinuation(invokeState); 423 | } 424 | } 425 | } 426 | } 427 | -------------------------------------------------------------------------------- /src/ValueTaskSupplement/ValueTaskEx.WhenAny_Array_NonGenerics.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.ExceptionServices; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using System.Threading.Tasks.Sources; 8 | 9 | namespace ValueTaskSupplement 10 | { 11 | public static partial class ValueTaskEx 12 | { 13 | public static ValueTask WhenAny(ValueTask left, Task right) 14 | { 15 | return new ValueTask(new WhenAnyPromise2(left, new ValueTask(right)), 0); 16 | } 17 | 18 | public static ValueTask WhenAny(IEnumerable tasks) 19 | { 20 | return new ValueTask(new WhenAnyPromiseAll(tasks), 0); 21 | } 22 | 23 | class WhenAnyPromiseAll : IValueTaskSource 24 | { 25 | static readonly ContextCallback execContextCallback = ExecutionContextCallback; 26 | static readonly SendOrPostCallback syncContextCallback = SynchronizationContextCallback; 27 | 28 | int completedCount = 0; 29 | ExceptionDispatchInfo? exception; 30 | Action continuation = ContinuationSentinel.AvailableContinuation; 31 | Action? invokeContinuation; 32 | object? state; 33 | SynchronizationContext? syncContext; 34 | ExecutionContext? execContext; 35 | 36 | int winArgumentIndex = -1; 37 | 38 | public WhenAnyPromiseAll(IEnumerable tasks) 39 | { 40 | var i = 0; 41 | foreach (var task in tasks) 42 | { 43 | var awaiter = task.GetAwaiter(); 44 | if (awaiter.IsCompleted) 45 | { 46 | try 47 | { 48 | awaiter.GetResult(); 49 | TryInvokeContinuationWithIndex(i); 50 | return; 51 | } 52 | catch (Exception ex) 53 | { 54 | exception = ExceptionDispatchInfo.Capture(ex); 55 | return; 56 | } 57 | } 58 | else 59 | { 60 | RegisterContinuation(awaiter, i); 61 | } 62 | 63 | i++; 64 | } 65 | } 66 | 67 | void RegisterContinuation(ValueTaskAwaiter awaiter, int index) 68 | { 69 | awaiter.UnsafeOnCompleted(() => 70 | { 71 | try 72 | { 73 | awaiter.GetResult(); 74 | TryInvokeContinuationWithIndex(index); 75 | return; 76 | } 77 | catch (Exception ex) 78 | { 79 | exception = ExceptionDispatchInfo.Capture(ex); 80 | TryInvokeContinuation(); 81 | return; 82 | } 83 | }); 84 | } 85 | 86 | void TryInvokeContinuationWithIndex(int winIndex) 87 | { 88 | if (Interlocked.Increment(ref completedCount) == 1) 89 | { 90 | Volatile.Write(ref winArgumentIndex, winIndex); 91 | TryInvokeContinuation(); 92 | } 93 | } 94 | 95 | void TryInvokeContinuation() 96 | { 97 | var c = Interlocked.Exchange(ref continuation, ContinuationSentinel.CompletedContinuation); 98 | if (c != ContinuationSentinel.AvailableContinuation && c != ContinuationSentinel.CompletedContinuation) 99 | { 100 | var spinWait = new SpinWait(); 101 | while (state == null) // worst case, state is not set yet so wait. 102 | { 103 | spinWait.SpinOnce(); 104 | } 105 | 106 | if (execContext != null) 107 | { 108 | invokeContinuation = c; 109 | ExecutionContext.Run(execContext, execContextCallback, this); 110 | } 111 | else if (syncContext != null) 112 | { 113 | invokeContinuation = c; 114 | syncContext.Post(syncContextCallback, this); 115 | } 116 | else 117 | { 118 | c(state); 119 | } 120 | } 121 | } 122 | 123 | public int GetResult(short token) 124 | { 125 | if (exception != null) 126 | { 127 | exception.Throw(); 128 | } 129 | return winArgumentIndex; 130 | } 131 | 132 | public ValueTaskSourceStatus GetStatus(short token) 133 | { 134 | return (Volatile.Read(ref winArgumentIndex) != -1) ? ValueTaskSourceStatus.Succeeded 135 | : (exception != null) ? ((exception.SourceException is OperationCanceledException) ? ValueTaskSourceStatus.Canceled : ValueTaskSourceStatus.Faulted) 136 | : ValueTaskSourceStatus.Pending; 137 | } 138 | 139 | public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) 140 | { 141 | var c = Interlocked.CompareExchange(ref this.continuation, continuation, ContinuationSentinel.AvailableContinuation); 142 | if (c == ContinuationSentinel.CompletedContinuation) 143 | { 144 | continuation(state); 145 | return; 146 | } 147 | 148 | if (c != ContinuationSentinel.AvailableContinuation) 149 | { 150 | throw new InvalidOperationException("does not allow multiple await."); 151 | } 152 | 153 | if (state == null) 154 | { 155 | throw new InvalidOperationException("invalid state."); 156 | } 157 | 158 | if ((flags & ValueTaskSourceOnCompletedFlags.FlowExecutionContext) == ValueTaskSourceOnCompletedFlags.FlowExecutionContext) 159 | { 160 | execContext = ExecutionContext.Capture(); 161 | } 162 | if ((flags & ValueTaskSourceOnCompletedFlags.UseSchedulingContext) == ValueTaskSourceOnCompletedFlags.UseSchedulingContext) 163 | { 164 | syncContext = SynchronizationContext.Current; 165 | } 166 | this.state = state; 167 | 168 | if (GetStatus(token) != ValueTaskSourceStatus.Pending) 169 | { 170 | TryInvokeContinuation(); 171 | } 172 | } 173 | 174 | static void ExecutionContextCallback(object state) 175 | { 176 | var self = (WhenAnyPromiseAll)state; 177 | if (self.syncContext != null) 178 | { 179 | self.syncContext.Post(syncContextCallback, self); 180 | } 181 | else 182 | { 183 | var invokeContinuation = self.invokeContinuation!; 184 | var invokeState = self.state; 185 | self.invokeContinuation = null; 186 | self.state = null; 187 | invokeContinuation(invokeState); 188 | } 189 | } 190 | 191 | static void SynchronizationContextCallback(object state) 192 | { 193 | var self = (WhenAnyPromiseAll)state; 194 | var invokeContinuation = self.invokeContinuation!; 195 | var invokeState = self.state; 196 | self.invokeContinuation = null; 197 | self.state = null; 198 | invokeContinuation(invokeState); 199 | } 200 | } 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/ValueTaskSupplement/ValueTaskEx.WhenAny_NonGenerics.tt: -------------------------------------------------------------------------------- 1 | <#@ template debug="false" hostspecific="false" language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Linq" #> 4 | <#@ import namespace="System.Text" #> 5 | <#@ import namespace="System.Collections.Generic" #> 6 | <#@ output extension=".cs" #> 7 | using System; 8 | using System.Runtime.CompilerServices; 9 | using System.Runtime.ExceptionServices; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | using System.Threading.Tasks.Sources; 13 | 14 | namespace ValueTaskSupplement 15 | { 16 | public static partial class ValueTaskEx 17 | { 18 | <# for(var i = 1; i <= 15; i++ ) { 19 | var range = Enumerable.Range(0, i + 1); 20 | var ttuple = string.Join(", ", range.Select(x => $"T{x} result{x}")); 21 | var args = string.Join(", ", range.Select(x => $"ValueTask task{x}")); 22 | var targs = string.Join(", ", range.Select(x => $"task{x}")); 23 | var tresultTuple = string.Join(", ", range.Select(x => $"t{x}")); 24 | #> 25 | public static ValueTask WhenAny(<#= args #>) 26 | { 27 | return new ValueTask(new WhenAnyPromise<#= i + 1 #>(<#= targs #>), 0); 28 | } 29 | 30 | class WhenAnyPromise<#= i + 1 #> : IValueTaskSource 31 | { 32 | static readonly ContextCallback execContextCallback = ExecutionContextCallback; 33 | static readonly SendOrPostCallback syncContextCallback = SynchronizationContextCallback; 34 | 35 | <# for(var j = 0; j <= i; j++) { #> 36 | ValueTaskAwaiter awaiter<#= j #>; 37 | <# } #> 38 | 39 | int completedCount = 0; 40 | int winArgumentIndex = -1; 41 | ExceptionDispatchInfo? exception; 42 | Action continuation = ContinuationSentinel.AvailableContinuation; 43 | Action? invokeContinuation; 44 | object? state; 45 | SynchronizationContext? syncContext; 46 | ExecutionContext? execContext; 47 | 48 | public WhenAnyPromise<#= i + 1 #>(<#= args #>) 49 | { 50 | <# for(var j = 0; j <= i; j++) { #> 51 | { 52 | var awaiter = task<#= j #>.GetAwaiter(); 53 | if (awaiter.IsCompleted) 54 | { 55 | try 56 | { 57 | awaiter.GetResult(); 58 | TryInvokeContinuationWithIncrement(<#= j #>); 59 | return; 60 | } 61 | catch (Exception ex) 62 | { 63 | exception = ExceptionDispatchInfo.Capture(ex); 64 | return; 65 | } 66 | } 67 | else 68 | { 69 | awaiter<#= j #> = awaiter; 70 | awaiter.UnsafeOnCompleted(Continuation<#= j #>); 71 | } 72 | } 73 | <# } #> 74 | } 75 | 76 | <# for(var j = 0; j <= i; j++) { #> 77 | void Continuation<#= j #>() 78 | { 79 | try 80 | { 81 | awaiter<#= j #>.GetResult(); 82 | } 83 | catch (Exception ex) 84 | { 85 | exception = ExceptionDispatchInfo.Capture(ex); 86 | TryInvokeContinuation(); 87 | return; 88 | } 89 | TryInvokeContinuationWithIncrement(<#= j #>); 90 | } 91 | 92 | <# } #> 93 | 94 | void TryInvokeContinuationWithIncrement(int index) 95 | { 96 | if (Interlocked.Increment(ref completedCount) == 1) 97 | { 98 | Volatile.Write(ref winArgumentIndex, index); 99 | TryInvokeContinuation(); 100 | } 101 | } 102 | 103 | void TryInvokeContinuation() 104 | { 105 | var c = Interlocked.Exchange(ref continuation, ContinuationSentinel.CompletedContinuation); 106 | if (c != ContinuationSentinel.AvailableContinuation && c != ContinuationSentinel.CompletedContinuation) 107 | { 108 | var spinWait = new SpinWait(); 109 | while (state == null) // worst case, state is not set yet so wait. 110 | { 111 | spinWait.SpinOnce(); 112 | } 113 | 114 | if (execContext != null) 115 | { 116 | invokeContinuation = c; 117 | ExecutionContext.Run(execContext, execContextCallback, this); 118 | } 119 | else if (syncContext != null) 120 | { 121 | invokeContinuation = c; 122 | syncContext.Post(syncContextCallback, this); 123 | } 124 | else 125 | { 126 | c(state); 127 | } 128 | } 129 | } 130 | 131 | public int GetResult(short token) 132 | { 133 | if (exception != null) 134 | { 135 | exception.Throw(); 136 | } 137 | return winArgumentIndex; 138 | } 139 | 140 | public ValueTaskSourceStatus GetStatus(short token) 141 | { 142 | return (Volatile.Read(ref winArgumentIndex) != -1) ? ValueTaskSourceStatus.Succeeded 143 | : (exception != null) ? ((exception.SourceException is OperationCanceledException) ? ValueTaskSourceStatus.Canceled : ValueTaskSourceStatus.Faulted) 144 | : ValueTaskSourceStatus.Pending; 145 | } 146 | 147 | public void OnCompleted(Action continuation, object? state, short token, ValueTaskSourceOnCompletedFlags flags) 148 | { 149 | var c = Interlocked.CompareExchange(ref this.continuation, continuation, ContinuationSentinel.AvailableContinuation); 150 | if (c == ContinuationSentinel.CompletedContinuation) 151 | { 152 | continuation(state); 153 | return; 154 | } 155 | 156 | if (c != ContinuationSentinel.AvailableContinuation) 157 | { 158 | throw new InvalidOperationException("does not allow multiple await."); 159 | } 160 | 161 | if (state == null) 162 | { 163 | throw new InvalidOperationException("invalid state."); 164 | } 165 | 166 | if ((flags & ValueTaskSourceOnCompletedFlags.FlowExecutionContext) == ValueTaskSourceOnCompletedFlags.FlowExecutionContext) 167 | { 168 | execContext = ExecutionContext.Capture(); 169 | } 170 | if ((flags & ValueTaskSourceOnCompletedFlags.UseSchedulingContext) == ValueTaskSourceOnCompletedFlags.UseSchedulingContext) 171 | { 172 | syncContext = SynchronizationContext.Current; 173 | } 174 | this.state = state; 175 | 176 | if (GetStatus(token) != ValueTaskSourceStatus.Pending) 177 | { 178 | TryInvokeContinuation(); 179 | } 180 | } 181 | 182 | static void ExecutionContextCallback(object state) 183 | { 184 | var self = (WhenAnyPromise<#= i + 1 #>)state; 185 | if (self.syncContext != null) 186 | { 187 | self.syncContext.Post(syncContextCallback, self); 188 | } 189 | else 190 | { 191 | var invokeContinuation = self.invokeContinuation!; 192 | var invokeState = self.state; 193 | self.invokeContinuation = null; 194 | self.state = null; 195 | invokeContinuation(invokeState); 196 | } 197 | } 198 | 199 | static void SynchronizationContextCallback(object state) 200 | { 201 | var self = (WhenAnyPromise<#= i + 1 #>)state; 202 | var invokeContinuation = self.invokeContinuation!; 203 | var invokeState = self.state; 204 | self.invokeContinuation = null; 205 | self.state = null; 206 | invokeContinuation(invokeState); 207 | } 208 | } 209 | 210 | <# } #> 211 | } 212 | } -------------------------------------------------------------------------------- /src/ValueTaskSupplement/ValueTaskSupplement.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 8.0 6 | enable 7 | Library 8 | False 9 | Cysharp 10 | true 11 | 1701;1702;1705;1591 12 | 13 | 14 | ValueTaskSupplement 15 | $(Version) 16 | Cysharp 17 | Cysharp 18 | Append supplemental methods(WhenAny, WhenAll, Lazy) to ValueTask. 19 | https://github.com/Cysharp/ValueTaskSupplement 20 | $(PackageProjectUrl) 21 | git 22 | async 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | TextTemplatingFileGenerator 34 | ValueTaskEx.WhenAll.cs 35 | 36 | 37 | ValueTaskEx.WhenAll_NonGenerics.cs 38 | TextTemplatingFileGenerator 39 | 40 | 41 | TextTemplatingFileGenerator 42 | ValueTaskEx.WhenAny.cs 43 | 44 | 45 | ValueTaskEx.WhenAny_NonGenerics.cs 46 | TextTemplatingFileGenerator 47 | 48 | 49 | TextTemplatingFileGenerator 50 | ValueTaskWhenAllExtensions.cs 51 | 52 | 53 | 54 | 55 | 56 | True 57 | True 58 | ValueTaskEx.WhenAll.tt 59 | 60 | 61 | True 62 | True 63 | ValueTaskEx.WhenAll_NonGenerics.tt 64 | 65 | 66 | True 67 | True 68 | ValueTaskEx.WhenAny.tt 69 | 70 | 71 | True 72 | True 73 | ValueTaskEx.WhenAny_NonGenerics.tt 74 | 75 | 76 | True 77 | True 78 | ValueTaskWhenAllExtensions.tt 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/ValueTaskSupplement/ValueTaskWhenAllExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using System.Runtime.CompilerServices; 3 | using System.Collections.Generic; 4 | 5 | namespace ValueTaskSupplement 6 | { 7 | public static class ValueTaskWhenAllExtensions 8 | { 9 | #region Generics 10 | public static ValueTaskAwaiter GetAwaiter(this IEnumerable> tasks) 11 | { 12 | return ValueTaskEx.WhenAll(tasks).GetAwaiter(); 13 | } 14 | 15 | public static ValueTaskAwaiter<(T0, T1)> GetAwaiter(this (ValueTask task0, ValueTask task1) tasks) 16 | { 17 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2).GetAwaiter(); 18 | } 19 | 20 | public static ValueTaskAwaiter<(T0, T1, T2)> GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2) tasks) 21 | { 22 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3).GetAwaiter(); 23 | } 24 | 25 | public static ValueTaskAwaiter<(T0, T1, T2, T3)> GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3) tasks) 26 | { 27 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4).GetAwaiter(); 28 | } 29 | 30 | public static ValueTaskAwaiter<(T0, T1, T2, T3, T4)> GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3, ValueTask task4) tasks) 31 | { 32 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5).GetAwaiter(); 33 | } 34 | 35 | public static ValueTaskAwaiter<(T0, T1, T2, T3, T4, T5)> GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3, ValueTask task4, ValueTask task5) tasks) 36 | { 37 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6).GetAwaiter(); 38 | } 39 | 40 | public static ValueTaskAwaiter<(T0, T1, T2, T3, T4, T5, T6)> GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3, ValueTask task4, ValueTask task5, ValueTask task6) tasks) 41 | { 42 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6, tasks.Item7).GetAwaiter(); 43 | } 44 | 45 | public static ValueTaskAwaiter<(T0, T1, T2, T3, T4, T5, T6, T7)> GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3, ValueTask task4, ValueTask task5, ValueTask task6, ValueTask task7) tasks) 46 | { 47 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6, tasks.Item7, tasks.Item8).GetAwaiter(); 48 | } 49 | 50 | public static ValueTaskAwaiter<(T0, T1, T2, T3, T4, T5, T6, T7, T8)> GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3, ValueTask task4, ValueTask task5, ValueTask task6, ValueTask task7, ValueTask task8) tasks) 51 | { 52 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6, tasks.Item7, tasks.Item8, tasks.Item9).GetAwaiter(); 53 | } 54 | 55 | public static ValueTaskAwaiter<(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9)> GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3, ValueTask task4, ValueTask task5, ValueTask task6, ValueTask task7, ValueTask task8, ValueTask task9) tasks) 56 | { 57 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6, tasks.Item7, tasks.Item8, tasks.Item9, tasks.Item10).GetAwaiter(); 58 | } 59 | 60 | public static ValueTaskAwaiter<(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10)> GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3, ValueTask task4, ValueTask task5, ValueTask task6, ValueTask task7, ValueTask task8, ValueTask task9, ValueTask task10) tasks) 61 | { 62 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6, tasks.Item7, tasks.Item8, tasks.Item9, tasks.Item10, tasks.Item11).GetAwaiter(); 63 | } 64 | 65 | public static ValueTaskAwaiter<(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11)> GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3, ValueTask task4, ValueTask task5, ValueTask task6, ValueTask task7, ValueTask task8, ValueTask task9, ValueTask task10, ValueTask task11) tasks) 66 | { 67 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6, tasks.Item7, tasks.Item8, tasks.Item9, tasks.Item10, tasks.Item11, tasks.Item12).GetAwaiter(); 68 | } 69 | 70 | public static ValueTaskAwaiter<(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12)> GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3, ValueTask task4, ValueTask task5, ValueTask task6, ValueTask task7, ValueTask task8, ValueTask task9, ValueTask task10, ValueTask task11, ValueTask task12) tasks) 71 | { 72 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6, tasks.Item7, tasks.Item8, tasks.Item9, tasks.Item10, tasks.Item11, tasks.Item12, tasks.Item13).GetAwaiter(); 73 | } 74 | 75 | public static ValueTaskAwaiter<(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13)> GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3, ValueTask task4, ValueTask task5, ValueTask task6, ValueTask task7, ValueTask task8, ValueTask task9, ValueTask task10, ValueTask task11, ValueTask task12, ValueTask task13) tasks) 76 | { 77 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6, tasks.Item7, tasks.Item8, tasks.Item9, tasks.Item10, tasks.Item11, tasks.Item12, tasks.Item13, tasks.Item14).GetAwaiter(); 78 | } 79 | 80 | public static ValueTaskAwaiter<(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14)> GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3, ValueTask task4, ValueTask task5, ValueTask task6, ValueTask task7, ValueTask task8, ValueTask task9, ValueTask task10, ValueTask task11, ValueTask task12, ValueTask task13, ValueTask task14) tasks) 81 | { 82 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6, tasks.Item7, tasks.Item8, tasks.Item9, tasks.Item10, tasks.Item11, tasks.Item12, tasks.Item13, tasks.Item14, tasks.Item15).GetAwaiter(); 83 | } 84 | 85 | public static ValueTaskAwaiter<(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15)> GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3, ValueTask task4, ValueTask task5, ValueTask task6, ValueTask task7, ValueTask task8, ValueTask task9, ValueTask task10, ValueTask task11, ValueTask task12, ValueTask task13, ValueTask task14, ValueTask task15) tasks) 86 | { 87 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6, tasks.Item7, tasks.Item8, tasks.Item9, tasks.Item10, tasks.Item11, tasks.Item12, tasks.Item13, tasks.Item14, tasks.Item15, tasks.Item16).GetAwaiter(); 88 | } 89 | #endregion 90 | 91 | #region Non Generics 92 | public static ValueTaskAwaiter GetAwaiter(this IEnumerable tasks) 93 | { 94 | return ValueTaskEx.WhenAll(tasks).GetAwaiter(); 95 | } 96 | 97 | public static ValueTaskAwaiter GetAwaiter(this (ValueTask task0, ValueTask task1) tasks) 98 | { 99 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2).GetAwaiter(); 100 | } 101 | 102 | public static ValueTaskAwaiter GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2) tasks) 103 | { 104 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3).GetAwaiter(); 105 | } 106 | 107 | public static ValueTaskAwaiter GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3) tasks) 108 | { 109 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4).GetAwaiter(); 110 | } 111 | 112 | public static ValueTaskAwaiter GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3, ValueTask task4) tasks) 113 | { 114 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5).GetAwaiter(); 115 | } 116 | 117 | public static ValueTaskAwaiter GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3, ValueTask task4, ValueTask task5) tasks) 118 | { 119 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6).GetAwaiter(); 120 | } 121 | 122 | public static ValueTaskAwaiter GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3, ValueTask task4, ValueTask task5, ValueTask task6) tasks) 123 | { 124 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6, tasks.Item7).GetAwaiter(); 125 | } 126 | 127 | public static ValueTaskAwaiter GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3, ValueTask task4, ValueTask task5, ValueTask task6, ValueTask task7) tasks) 128 | { 129 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6, tasks.Item7, tasks.Item8).GetAwaiter(); 130 | } 131 | 132 | public static ValueTaskAwaiter GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3, ValueTask task4, ValueTask task5, ValueTask task6, ValueTask task7, ValueTask task8) tasks) 133 | { 134 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6, tasks.Item7, tasks.Item8, tasks.Item9).GetAwaiter(); 135 | } 136 | 137 | public static ValueTaskAwaiter GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3, ValueTask task4, ValueTask task5, ValueTask task6, ValueTask task7, ValueTask task8, ValueTask task9) tasks) 138 | { 139 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6, tasks.Item7, tasks.Item8, tasks.Item9, tasks.Item10).GetAwaiter(); 140 | } 141 | 142 | public static ValueTaskAwaiter GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3, ValueTask task4, ValueTask task5, ValueTask task6, ValueTask task7, ValueTask task8, ValueTask task9, ValueTask task10) tasks) 143 | { 144 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6, tasks.Item7, tasks.Item8, tasks.Item9, tasks.Item10, tasks.Item11).GetAwaiter(); 145 | } 146 | 147 | public static ValueTaskAwaiter GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3, ValueTask task4, ValueTask task5, ValueTask task6, ValueTask task7, ValueTask task8, ValueTask task9, ValueTask task10, ValueTask task11) tasks) 148 | { 149 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6, tasks.Item7, tasks.Item8, tasks.Item9, tasks.Item10, tasks.Item11, tasks.Item12).GetAwaiter(); 150 | } 151 | 152 | public static ValueTaskAwaiter GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3, ValueTask task4, ValueTask task5, ValueTask task6, ValueTask task7, ValueTask task8, ValueTask task9, ValueTask task10, ValueTask task11, ValueTask task12) tasks) 153 | { 154 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6, tasks.Item7, tasks.Item8, tasks.Item9, tasks.Item10, tasks.Item11, tasks.Item12, tasks.Item13).GetAwaiter(); 155 | } 156 | 157 | public static ValueTaskAwaiter GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3, ValueTask task4, ValueTask task5, ValueTask task6, ValueTask task7, ValueTask task8, ValueTask task9, ValueTask task10, ValueTask task11, ValueTask task12, ValueTask task13) tasks) 158 | { 159 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6, tasks.Item7, tasks.Item8, tasks.Item9, tasks.Item10, tasks.Item11, tasks.Item12, tasks.Item13, tasks.Item14).GetAwaiter(); 160 | } 161 | 162 | public static ValueTaskAwaiter GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3, ValueTask task4, ValueTask task5, ValueTask task6, ValueTask task7, ValueTask task8, ValueTask task9, ValueTask task10, ValueTask task11, ValueTask task12, ValueTask task13, ValueTask task14) tasks) 163 | { 164 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6, tasks.Item7, tasks.Item8, tasks.Item9, tasks.Item10, tasks.Item11, tasks.Item12, tasks.Item13, tasks.Item14, tasks.Item15).GetAwaiter(); 165 | } 166 | 167 | public static ValueTaskAwaiter GetAwaiter(this (ValueTask task0, ValueTask task1, ValueTask task2, ValueTask task3, ValueTask task4, ValueTask task5, ValueTask task6, ValueTask task7, ValueTask task8, ValueTask task9, ValueTask task10, ValueTask task11, ValueTask task12, ValueTask task13, ValueTask task14, ValueTask task15) tasks) 168 | { 169 | return ValueTaskEx.WhenAll(tasks.Item1, tasks.Item2, tasks.Item3, tasks.Item4, tasks.Item5, tasks.Item6, tasks.Item7, tasks.Item8, tasks.Item9, tasks.Item10, tasks.Item11, tasks.Item12, tasks.Item13, tasks.Item14, tasks.Item15, tasks.Item16).GetAwaiter(); 170 | } 171 | #endregion 172 | } 173 | } -------------------------------------------------------------------------------- /src/ValueTaskSupplement/ValueTaskWhenAllExtensions.tt: -------------------------------------------------------------------------------- 1 | <#@ template debug="false" hostspecific="false" language="C#" #> 2 | <#@ assembly name="System.Core" #> 3 | <#@ import namespace="System.Linq" #> 4 | <#@ import namespace="System.Text" #> 5 | <#@ import namespace="System.Collections.Generic" #> 6 | <#@ output extension=".cs" #> 7 | using System.Threading.Tasks; 8 | using System.Runtime.CompilerServices; 9 | using System.Collections.Generic; 10 | 11 | namespace ValueTaskSupplement 12 | { 13 | public static class ValueTaskWhenAllExtensions 14 | { 15 | #region Generics 16 | public static ValueTaskAwaiter GetAwaiter(this IEnumerable> tasks) 17 | { 18 | return ValueTaskEx.WhenAll(tasks).GetAwaiter(); 19 | } 20 | <# for(var i = 1; i <= 15; i++) { 21 | var range = Enumerable.Range(0, i + 1); 22 | var t = string.Join(", ", range.Select(x => "T" + x)); 23 | var args = string.Join(", ", range.Select(x => $"ValueTask task{x}")); 24 | var itemx = string.Join(", ", range.Select(x => $"tasks.Item{x + 1}")); 25 | #> 26 | 27 | public static ValueTaskAwaiter<(<#= t #>)> GetAwaiter<<#= t #>>(this (<#= args #>) tasks) 28 | { 29 | return ValueTaskEx.WhenAll(<#= itemx #>).GetAwaiter(); 30 | } 31 | <# }#> 32 | #endregion 33 | 34 | #region Non Generics 35 | public static ValueTaskAwaiter GetAwaiter(this IEnumerable tasks) 36 | { 37 | return ValueTaskEx.WhenAll(tasks).GetAwaiter(); 38 | } 39 | <# for(var i = 1; i <= 15; i++) { 40 | var range = Enumerable.Range(0, i + 1); 41 | var args = string.Join(", ", range.Select(x => $"ValueTask task{x}")); 42 | var itemx = string.Join(", ", range.Select(x => $"tasks.Item{x + 1}")); 43 | #> 44 | 45 | public static ValueTaskAwaiter GetAwaiter(this (<#= args #>) tasks) 46 | { 47 | return ValueTaskEx.WhenAll(<#= itemx #>).GetAwaiter(); 48 | } 49 | <# }#> 50 | #endregion 51 | } 52 | } -------------------------------------------------------------------------------- /src/ValueTaskSupplement/_InternalVisibleTo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("ValueTaskSupplement.Tests")] -------------------------------------------------------------------------------- /tests/ValueTaskSupplement.Tests/LazyTest.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable CS1998 2 | 3 | using FluentAssertions; 4 | using System; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace ValueTaskSupplement.Tests 9 | { 10 | public class LazyTest 11 | { 12 | [Fact] 13 | public async Task Sync() 14 | { 15 | var calledCount = 0; 16 | var syncLazy = ValueTaskEx.Lazy(async () => { calledCount++; return 100; }); 17 | 18 | calledCount.Should().Be(0); 19 | 20 | var value = await syncLazy; 21 | value.Should().Be(100); 22 | calledCount.Should().Be(1); 23 | 24 | var value2 = await syncLazy; 25 | value.Should().Be(100); 26 | 27 | calledCount.Should().Be(1); 28 | } 29 | 30 | [Fact] 31 | public async Task Async() 32 | { 33 | var calledCount = 0; 34 | var asyncLazyOriginal = ValueTaskEx.Lazy(async () => { calledCount++; await Task.Delay(TimeSpan.FromSeconds(1)); return new object(); }); 35 | var asyncLazy = asyncLazyOriginal; 36 | calledCount.Should().Be(0); 37 | 38 | var (v1, v2, v3) = await ValueTaskEx.WhenAll(asyncLazy.AsValueTask(), asyncLazy.AsValueTask(), asyncLazyOriginal.AsValueTask()); 39 | 40 | calledCount.Should().Be(1); 41 | 42 | object.ReferenceEquals(v1, v2).Should().BeTrue(); 43 | object.ReferenceEquals(v2, v3).Should().BeTrue(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/ValueTaskSupplement.Tests/LazyTest_NonGenerics.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable CS1998 2 | 3 | using System; 4 | using System.Threading.Tasks; 5 | using FluentAssertions; 6 | using Xunit; 7 | 8 | namespace ValueTaskSupplement.Tests 9 | { 10 | public class LazyTest_NonGenerics 11 | { 12 | [Fact] 13 | public async Task Sync() 14 | { 15 | var calledCount = 0; 16 | var syncLazy = ValueTaskEx.Lazy(async () => { calledCount++; }); 17 | 18 | calledCount.Should().Be(0); 19 | 20 | await syncLazy; 21 | calledCount.Should().Be(1); 22 | 23 | await syncLazy; 24 | calledCount.Should().Be(1); 25 | } 26 | 27 | [Fact] 28 | public async Task Async() 29 | { 30 | var calledCount = 0; 31 | var syncLazy = ValueTaskEx.Lazy(async () => { calledCount++; await Task.Delay(TimeSpan.FromSeconds(1)); }); 32 | calledCount.Should().Be(0); 33 | 34 | await ValueTaskEx.WhenAll(syncLazy, syncLazy, syncLazy); 35 | 36 | calledCount.Should().Be(1); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/ValueTaskSupplement.Tests/TempListTest.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using Xunit; 7 | 8 | namespace ValueTaskSupplement.Tests 9 | { 10 | public class TempListTest 11 | { 12 | [Fact] 13 | public void Foo() 14 | { 15 | var xs = new TempList(6); 16 | xs.Add(10); 17 | xs.Add(20); 18 | xs.Add(30); 19 | xs.Add(40); 20 | xs.Add(50); 21 | 22 | xs.AsSpan().SequenceEqual(new[] { 10, 20, 30, 40, 50 }).Should().BeTrue(); 23 | 24 | xs.Dispose(); 25 | 26 | var ys = new TempList(3); 27 | 28 | foreach (var item in Enumerable.Range(1, 100)) 29 | { 30 | ys.Add(item); 31 | } 32 | 33 | ys.AsSpan().SequenceEqual(Enumerable.Range(1, 100).ToArray()).Should().BeTrue(); 34 | 35 | ys.Dispose(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/ValueTaskSupplement.Tests/ValueTaskSupplement.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/ValueTaskSupplement.Tests/WhenAllTest.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace ValueTaskSupplement.Tests 9 | { 10 | public class WhenAllTest 11 | { 12 | [Fact] 13 | public async Task AllSync() 14 | { 15 | var a = CreateSync(1); 16 | var b = CreateSync(2); 17 | var c = CreateSync(3); 18 | var result = await ValueTaskEx.WhenAll(a, b, c); 19 | result.Should().Be((1, 2, 3)); 20 | } 21 | 22 | [Fact] 23 | public async Task WithAsync() 24 | { 25 | var a = CreateSync(1); 26 | var b = CreateAsync(2); 27 | var c = CreateAsync(3); 28 | var result = await ValueTaskEx.WhenAll(a, b, c); 29 | result.Should().Be((1, 2, 3)); 30 | } 31 | 32 | [Fact] 33 | public async Task Array() 34 | { 35 | var a = CreateSync(1); 36 | var b = CreateAsync(2); 37 | var c = CreateAsync(3); 38 | var result = await ValueTaskEx.WhenAll(new[] { a, b, c }); 39 | result.Should().BeEquivalentTo(new[] { 1, 2, 3 }); 40 | } 41 | 42 | [Fact] 43 | public async Task Extension() 44 | { 45 | var result = await new[] { CreateAsync(1), CreateAsync(2), CreateAsync(3) }; 46 | result[0].Should().Be(1); 47 | result[1].Should().Be(2); 48 | result[2].Should().Be(3); 49 | 50 | var (x, y) = await (CreateAsync(10), CreateAsync(99)); 51 | x.Should().Be(10); 52 | y.Should().Be(99); 53 | } 54 | 55 | ValueTask CreateSync(int i) 56 | { 57 | return new ValueTask(i); 58 | } 59 | 60 | async ValueTask CreateAsync(int i) 61 | { 62 | await Task.Delay(TimeSpan.FromMilliseconds(10)); 63 | return i; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/ValueTaskSupplement.Tests/WhenAllTest_NonGenerics.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using FluentAssertions; 4 | using Xunit; 5 | 6 | namespace ValueTaskSupplement.Tests 7 | { 8 | public class WhenAllTest_NonGenerics 9 | { 10 | [Fact] 11 | public async Task AllSync() 12 | { 13 | var a = CreateSync(); 14 | var b = CreateSync(); 15 | var c = CreateSync(); 16 | 17 | a.IsCompletedSuccessfully.Should().BeTrue(); 18 | b.IsCompletedSuccessfully.Should().BeTrue(); 19 | c.IsCompletedSuccessfully.Should().BeTrue(); 20 | 21 | await ValueTaskEx.WhenAll(a, b, c); 22 | 23 | a.IsCompletedSuccessfully.Should().BeTrue(); 24 | b.IsCompletedSuccessfully.Should().BeTrue(); 25 | c.IsCompletedSuccessfully.Should().BeTrue(); 26 | } 27 | 28 | [Fact] 29 | public async Task WithAsync() 30 | { 31 | var a = CreateSync(); 32 | var b = CreateAsync(); 33 | var c = CreateAsync(); 34 | 35 | a.IsCompletedSuccessfully.Should().BeTrue(); 36 | b.IsCompletedSuccessfully.Should().BeFalse(); 37 | c.IsCompletedSuccessfully.Should().BeFalse(); 38 | 39 | await ValueTaskEx.WhenAll(a, b, c); 40 | 41 | a.IsCompletedSuccessfully.Should().BeTrue(); 42 | b.IsCompletedSuccessfully.Should().BeTrue(); 43 | c.IsCompletedSuccessfully.Should().BeTrue(); 44 | } 45 | 46 | [Fact] 47 | public async Task Array() 48 | { 49 | var a = CreateSync(); 50 | var b = CreateAsync(); 51 | var c = CreateAsync(); 52 | 53 | a.IsCompletedSuccessfully.Should().BeTrue(); 54 | b.IsCompletedSuccessfully.Should().BeFalse(); 55 | c.IsCompletedSuccessfully.Should().BeFalse(); 56 | 57 | await ValueTaskEx.WhenAll(new[] { a, b, c }); 58 | 59 | a.IsCompletedSuccessfully.Should().BeTrue(); 60 | b.IsCompletedSuccessfully.Should().BeTrue(); 61 | c.IsCompletedSuccessfully.Should().BeTrue(); 62 | } 63 | 64 | [Fact] 65 | public async Task Extension() 66 | { 67 | await new[] { CreateAsync(), CreateAsync(), CreateAsync() }; 68 | await (CreateAsync(), CreateAsync()); 69 | } 70 | 71 | ValueTask CreateSync() 72 | { 73 | return new ValueTask(); 74 | } 75 | 76 | async ValueTask CreateAsync() 77 | { 78 | await Task.Delay(TimeSpan.FromMilliseconds(10)); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tests/ValueTaskSupplement.Tests/WhenAnyTest.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Xunit; 7 | 8 | namespace ValueTaskSupplement.Tests 9 | { 10 | public class WhenAnyTest 11 | { 12 | [Fact] 13 | public async Task AnySync() 14 | { 15 | var a = CreateSync(1); 16 | var b = CreateSync(2); 17 | var c = CreateSync(3); 18 | var result = await ValueTaskEx.WhenAny(a, b, c); 19 | result.winArgumentIndex.Should().Be(0); 20 | 21 | result.result0.Should().Be(1); 22 | } 23 | 24 | [Fact] 25 | public async Task WithAsync() 26 | { 27 | var a = CreateAsync(1); 28 | var b = CreateSync(2); 29 | var c = CreateAsync(3); 30 | var result = await ValueTaskEx.WhenAny(a, b, c); 31 | result.winArgumentIndex.Should().Be(1); 32 | 33 | result.result1.Should().Be(2); 34 | } 35 | 36 | [Fact] 37 | public async Task Array() 38 | { 39 | var a = CreateSync(1); 40 | var b = CreateAsync(2); 41 | var c = CreateAsync(3); 42 | var result = await ValueTaskEx.WhenAny(new[] { a, b, c }); 43 | result.winArgumentIndex.Should().Be(0); 44 | result.result.Should().Be(1); 45 | } 46 | 47 | [Fact] 48 | public async Task Timeout() 49 | { 50 | { 51 | var delay = Task.Delay(TimeSpan.FromMilliseconds(100)); 52 | var vtask = CreateAsync(999); 53 | var (hasValue, value) = await ValueTaskEx.WhenAny(vtask, delay); 54 | hasValue.Should().BeTrue(); 55 | value.Should().Be(999); 56 | } 57 | { 58 | var delay = Task.Delay(TimeSpan.FromMilliseconds(100)); 59 | var vtask = CreateAsyncSlow(999); 60 | var (hasValue, value) = await ValueTaskEx.WhenAny(vtask, delay); 61 | hasValue.Should().BeFalse(); 62 | } 63 | { 64 | var delay = Task.Delay(TimeSpan.FromMilliseconds(100)); 65 | var vtask = CreateSync(999); 66 | var (hasValue, value) = await ValueTaskEx.WhenAny(vtask, delay); 67 | hasValue.Should().BeTrue(); 68 | value.Should().Be(999); 69 | } 70 | } 71 | 72 | ValueTask CreateSync(int i) 73 | { 74 | return new ValueTask(i); 75 | } 76 | 77 | async ValueTask CreateAsync(int i) 78 | { 79 | await Task.Delay(TimeSpan.FromMilliseconds(10)); 80 | return i; 81 | } 82 | 83 | async ValueTask CreateAsyncSlow(int i) 84 | { 85 | await Task.Delay(TimeSpan.FromMilliseconds(200)); 86 | return i; 87 | } 88 | 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /tests/ValueTaskSupplement.Tests/WhenAnyTest_NonGenerics.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using FluentAssertions; 4 | using Xunit; 5 | 6 | namespace ValueTaskSupplement.Tests 7 | { 8 | public class WhenAnyTest_NonGenerics 9 | { 10 | [Fact] 11 | public async Task AnySync() 12 | { 13 | var a = CreateSync(); 14 | var b = CreateSync(); 15 | var c = CreateSync(); 16 | var result = await ValueTaskEx.WhenAny(a, b, c); 17 | result.Should().Be(0); 18 | } 19 | 20 | [Fact] 21 | public async Task WithAsync() 22 | { 23 | var a = CreateAsync(); 24 | var b = CreateSync(); 25 | var c = CreateAsyncSlow(); 26 | var result = await ValueTaskEx.WhenAny(a, b, c); 27 | result.Should().Be(1); 28 | } 29 | 30 | [Fact] 31 | public async Task Array() 32 | { 33 | var a = CreateSync(); 34 | var b = CreateAsync(); 35 | var c = CreateAsyncSlow(); 36 | var result = await ValueTaskEx.WhenAny(new[] { a, b, c }); 37 | result.Should().Be(0); 38 | } 39 | 40 | [Fact] 41 | public async Task Timeout() 42 | { 43 | { 44 | var delay = Task.Delay(TimeSpan.FromMilliseconds(100)); 45 | var vtask = CreateAsync(); 46 | var index = await ValueTaskEx.WhenAny(vtask, delay); 47 | index.Should().Be(0); 48 | } 49 | { 50 | var delay = Task.Delay(TimeSpan.FromMilliseconds(100)); 51 | var vtask = CreateAsyncSlow(); 52 | var index = await ValueTaskEx.WhenAny(vtask, delay); 53 | index.Should().Be(1); 54 | } 55 | { 56 | var delay = Task.Delay(TimeSpan.FromMilliseconds(100)); 57 | var vtask = CreateSync(); 58 | var index = await ValueTaskEx.WhenAny(vtask, delay); 59 | index.Should().Be(0); 60 | } 61 | } 62 | 63 | ValueTask CreateSync() 64 | { 65 | return new ValueTask(); 66 | } 67 | 68 | async ValueTask CreateAsync() 69 | { 70 | await Task.Delay(TimeSpan.FromMilliseconds(10)); 71 | } 72 | 73 | async ValueTask CreateAsyncSlow() 74 | { 75 | await Task.Delay(TimeSpan.FromMilliseconds(200)); 76 | } 77 | } 78 | } 79 | --------------------------------------------------------------------------------