├── .github └── workflows │ └── build.yml ├── .gitignore ├── Directory.Build.props ├── FSharp.Control.FusionTasks.Tests.CS ├── FSharp.Control.FusionTasks.Tests.CS.csproj └── TaskExtensionsTests.cs ├── FSharp.Control.FusionTasks.Tests.FS ├── AsyncSequneceTests.fs ├── BasisTests.fs ├── ConfiguredTests.fs ├── FSharp.Control.FusionTasks.Tests.FS.fsproj └── OperatorTests.fs ├── FSharp.Control.FusionTasks.Tests.Utilities ├── AsyncDisposableFactory.cs ├── AsyncEnumerableFactory.cs ├── FSharp.Control.FusionTasks.Tests.Utilities.csproj └── ThreadBoundSynchronizationContext.cs ├── FSharp.Control.FusionTasks.sln ├── FSharp.Control.FusionTasks.sln.licenseheader ├── FSharp.Control.FusionTasks ├── AsyncCompletionSource.fs ├── AsyncExtensions.fs ├── Awaiters.fs ├── CommonAssemblyInfo.fs ├── FSharp.Control.FusionTasks.fsproj ├── Infrastructures.fs ├── TaskExtensions.fs └── Utilities.fs ├── Images ├── FSharp.Control.FusionTasks.100.png ├── FSharp.Control.FusionTasks.128.png ├── FSharp.Control.FusionTasks.design └── linqpad5.png ├── LICENSE.txt ├── NuGet.Config ├── README.md ├── build-nupkg.bat └── build-nupkg.sh /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: .NET 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v2 11 | with: 12 | fetch-depth: 0 13 | # lfs: true 14 | # https://stackoverflow.com/questions/61463578/github-actions-actions-checkoutv2-lfs-true-flag-not-converting-pointers-to-act 15 | #- name: Checkout LFS objects 16 | # run: git lfs checkout 17 | 18 | - name: Extract branch name 19 | id: extract_branch_name 20 | # if: startsWith( github.ref, 'refs/tags/' ) 21 | run: | 22 | export branch_name=`git name-rev --name-only --exclude=tags/* HEAD` 23 | echo "Detected current branch: $branch_name" 24 | echo "::set-output name=branch_name::$branch_name" 25 | 26 | # ---------------------------------------------------------- 27 | 28 | - name: Setup .NET Core 2.2 29 | uses: actions/setup-dotnet@v1 30 | with: 31 | dotnet-version: 2.2.* 32 | - name: Setup .NET Core 3.1 33 | uses: actions/setup-dotnet@v1 34 | with: 35 | dotnet-version: 3.1.* 36 | - name: Setup .NET 5 37 | uses: actions/setup-dotnet@v1 38 | with: 39 | dotnet-version: 5.0.* 40 | - name: Setup .NET 6 41 | uses: actions/setup-dotnet@v1 42 | with: 43 | dotnet-version: 6.0.* 44 | 45 | # ---------------------------------------------------------- 46 | 47 | - name: Setup NuGet package reference 48 | run: dotnet nuget add source ${{secrets.GH_LOCAL_NUGET_URL}} -n ref1 -u ${{secrets.GH_LOCAL_NUGET_USER}} -p ${{secrets.GH_LOCAL_NUGET_PASSWORD}} --store-password-in-clear-text --configfile NuGet.Config 49 | 50 | # ---------------------------------------------------------- 51 | 52 | - name: Build 53 | run: dotnet build -p:Configuration=Release -p:BuildIdentifier=${GITHUB_RUN_NUMBER} 54 | 55 | # ---------------------------------------------------------- 56 | 57 | - name: Test 58 | run: dotnet test --verbosity normal -p:Configuration=Release -p:BuildIdentifier=${GITHUB_RUN_NUMBER} 59 | timeout-minutes: 2 60 | 61 | # ---------------------------------------------------------- 62 | 63 | - name: Build packages 64 | run: dotnet pack -p:Configuration=Release -p:BuildIdentifier=${GITHUB_RUN_NUMBER} -o artifacts 65 | 66 | # ---------------------------------------------------------- 67 | 68 | - name: Deploy NuGet package (devel/ref1) 69 | if: startsWith( github.ref, 'refs/tags/' ) 70 | run: dotnet nuget push artifacts/FSharp.Control.FusionTasks.*.nupkg --source ref1 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | 24 | # Visual Studio 2015 cache/options directory 25 | .vs/ 26 | # Uncomment if you have tasks that create the project's static files in wwwroot 27 | #wwwroot/ 28 | 29 | # MSTest test Results 30 | [Tt]est[Rr]esult*/ 31 | [Bb]uild[Ll]og.* 32 | 33 | # NUNIT 34 | *.VisualState.xml 35 | TestResult.xml 36 | 37 | # Build Results of an ATL Project 38 | [Dd]ebugPS/ 39 | [Rr]eleasePS/ 40 | dlldata.c 41 | 42 | # DNX 43 | project.lock.json 44 | artifacts/ 45 | 46 | *_i.c 47 | *_p.c 48 | *_i.h 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.tmp_proj 63 | *.log 64 | *.vspscc 65 | *.vssscc 66 | .builds 67 | *.pidb 68 | *.svclog 69 | *.scc 70 | 71 | # Chutzpah Test files 72 | _Chutzpah* 73 | 74 | # Visual C++ cache files 75 | ipch/ 76 | *.aps 77 | *.ncb 78 | *.opendb 79 | *.opensdf 80 | *.sdf 81 | *.cachefile 82 | 83 | # Visual Studio profiler 84 | *.psess 85 | *.vsp 86 | *.vspx 87 | *.sap 88 | 89 | # TFS 2012 Local Workspace 90 | $tf/ 91 | 92 | # Guidance Automation Toolkit 93 | *.gpState 94 | 95 | # ReSharper is a .NET coding add-in 96 | _ReSharper*/ 97 | *.[Rr]e[Ss]harper 98 | *.DotSettings.user 99 | 100 | # JustCode is a .NET coding add-in 101 | .JustCode 102 | 103 | # TeamCity is a build add-in 104 | _TeamCity* 105 | 106 | # DotCover is a Code Coverage Tool 107 | *.dotCover 108 | 109 | # NCrunch 110 | _NCrunch_* 111 | .*crunch*.local.xml 112 | nCrunchTemp_* 113 | 114 | # MightyMoose 115 | *.mm.* 116 | AutoTest.Net/ 117 | 118 | # Web workbench (sass) 119 | .sass-cache/ 120 | 121 | # Installshield output folder 122 | [Ee]xpress/ 123 | 124 | # DocProject is a documentation generator add-in 125 | DocProject/buildhelp/ 126 | DocProject/Help/*.HxT 127 | DocProject/Help/*.HxC 128 | DocProject/Help/*.hhc 129 | DocProject/Help/*.hhk 130 | DocProject/Help/*.hhp 131 | DocProject/Help/Html2 132 | DocProject/Help/html 133 | 134 | # Click-Once directory 135 | publish/ 136 | 137 | # Publish Web Output 138 | *.[Pp]ublish.xml 139 | *.azurePubxml 140 | # TODO: Comment the next line if you want to checkin your web deploy settings 141 | # but database connection strings (with potential passwords) will be unencrypted 142 | *.pubxml 143 | *.publishproj 144 | 145 | # NuGet Packages 146 | *.nupkg 147 | # The packages folder can be ignored because of Package Restore 148 | **/packages/* 149 | # except build/, which is used as an MSBuild target. 150 | !**/packages/build/ 151 | # Uncomment if necessary however generally it will be regenerated when needed 152 | #!**/packages/repositories.config 153 | 154 | # Microsoft Azure Build Output 155 | csx/ 156 | *.build.csdef 157 | 158 | # Microsoft Azure Emulator 159 | ecf/ 160 | rcf/ 161 | 162 | # Microsoft Azure ApplicationInsights config file 163 | ApplicationInsights.config 164 | 165 | # Windows Store app package directory 166 | AppPackages/ 167 | BundleArtifacts/ 168 | 169 | # Visual Studio cache files 170 | # files ending in .cache can be ignored 171 | *.[Cc]ache 172 | # but keep track of directories ending in .cache 173 | !*.[Cc]ache/ 174 | 175 | # Others 176 | ClientBin/ 177 | ~$* 178 | *~ 179 | *.dbmdl 180 | *.dbproj.schemaview 181 | *.pfx 182 | *.publishsettings 183 | node_modules/ 184 | orleans.codegen.cs 185 | 186 | # RIA/Silverlight projects 187 | Generated_Code/ 188 | 189 | # Backup & report files from converting an old project file 190 | # to a newer Visual Studio version. Backup files are not needed, 191 | # because we have git ;-) 192 | _UpgradeReport_Files/ 193 | Backup*/ 194 | UpgradeLog*.XML 195 | UpgradeLog*.htm 196 | 197 | # SQL Server files 198 | *.mdf 199 | *.ldf 200 | 201 | # Business Intelligence projects 202 | *.rdl.data 203 | *.bim.layout 204 | *.bim_*.settings 205 | 206 | # Microsoft Fakes 207 | FakesAssemblies/ 208 | 209 | # GhostDoc plugin setting file 210 | *.GhostDoc.xml 211 | 212 | # Node.js Tools for Visual Studio 213 | .ntvs_analysis.dat 214 | 215 | # Visual Studio 6 build log 216 | *.plg 217 | 218 | # Visual Studio 6 workspace options file 219 | *.opt 220 | 221 | # Visual Studio LightSwitch build output 222 | **/*.HTMLClient/GeneratedArtifacts 223 | **/*.DesktopClient/GeneratedArtifacts 224 | **/*.DesktopClient/ModelManifest.xml 225 | **/*.Server/GeneratedArtifacts 226 | **/*.Server/ModelManifest.xml 227 | _Pvt_Extensions 228 | 229 | # Paket dependency manager 230 | .paket/paket.exe 231 | 232 | # FAKE - F# Make 233 | .fake/ 234 | 235 | .nuget/ 236 | 237 | .idea/ 238 | 239 | artifacts/ 240 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | latest 5 | enable 6 | AnyCPU 7 | 8 | true 9 | true 10 | false 11 | false 12 | true 13 | git 14 | https://github.com/kekyo/FSharp.Control.FusionTasks.git 15 | 16 | FlashCap 17 | false 18 | true 19 | $(NoWarn);CS1570;CS1591;CA1416 20 | 21 | FusionTasks 22 | FusionTasks 23 | FusionTasks 24 | Copyright (c) Kouji Matsui 25 | F# Async workflow <--> .NET Task easy seamless interoperability library. 26 | 27 | Kouji Matsui (@kozy_kekyo, @kekyo@mastodon.cloud) 28 | Kouji Matsui (@kozy_kekyo, @kekyo@mastodon.cloud) 29 | Apache-2.0 30 | https://github.com/kekyo/FSharp.Control.FusionTasks 31 | FSharp.Control.FusionTasks.100.png 32 | F#;FSharp;Async;Task;C#;CSharp;await;interop;seamless;computation;Builder;extension 33 | .pdb 34 | true 35 | $(NoWarn);NU1605;NU1701;NU1803 36 | false 37 | 38 | 39 | 40 | portable 41 | false 42 | false 43 | false 44 | 45 | 46 | 47 | embedded 48 | true 49 | true 50 | true 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /FSharp.Control.FusionTasks.Tests.CS/FSharp.Control.FusionTasks.Tests.CS.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net48;netcoreapp2.2;netcoreapp3.1;net5.0-windows;net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /FSharp.Control.FusionTasks.Tests.CS/TaskExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // FSharp.Control.FusionTasks - F# Async workflow <--> .NET Task easy seamless interoperability library. 4 | // Copyright (c) 2016-2021 Kouji Matsui (@kozy_kekyo, @kekyo2) 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | ///////////////////////////////////////////////////////////////////////////////////////////////// 19 | 20 | using System; 21 | using System.Collections.Generic; 22 | using System.Threading; 23 | using System.Threading.Tasks; 24 | using Microsoft.FSharp.Control; 25 | using Microsoft.FSharp.Core; 26 | 27 | using NUnit; 28 | using NUnit.Framework; 29 | 30 | namespace FSharp.Control.FusionTasks.Tests 31 | { 32 | [TestFixture] 33 | public class TaskExtensionsTests 34 | { 35 | #region Task.AsAsync 36 | private static async Task DelayAsync() 37 | { 38 | await Task.Delay(500); 39 | Console.WriteLine("AAA"); 40 | } 41 | 42 | [Test] 43 | public void TaskAsAsyncTest() 44 | { 45 | var task = DelayAsync(); 46 | var asy = task.AsAsync(); 47 | 48 | // MSTest not supported FSharpAsync based tests, so run synchronously here. 49 | FSharpAsync.RunSynchronously(asy, FSharpOption.None, FSharpOption.None); 50 | } 51 | 52 | [Test] 53 | public void ConfiguredAsyncAwaiterAsAsyncTest() 54 | { 55 | var task = DelayAsync(); 56 | var asy = task.AsAsyncConfigured(false); 57 | 58 | // MSTest not supported FSharpAsync based tests, so run synchronously here. 59 | FSharpAsync.RunSynchronously(asy, FSharpOption.None, FSharpOption.None); 60 | } 61 | 62 | private static async Task DelayAndReturnAsync() 63 | { 64 | await Task.Delay(500); 65 | return 123; 66 | } 67 | 68 | [Test] 69 | public void TaskTAsAsyncTest() 70 | { 71 | var task = DelayAndReturnAsync(); 72 | var asy = task.AsAsync(); 73 | 74 | // MSTest not supported FSharpAsync based tests, so run synchronously here. 75 | var result = FSharpAsync.RunSynchronously(asy, FSharpOption.None, FSharpOption.None); 76 | Assert.AreEqual(123, result); 77 | } 78 | 79 | [Test] 80 | public void ConfiguredAsyncAwaiterTAsAsyncTest() 81 | { 82 | var task = DelayAndReturnAsync(); 83 | var asy = task.AsAsyncConfigured(false); 84 | 85 | // MSTest not supported FSharpAsync based tests, so run synchronously here. 86 | var result = FSharpAsync.RunSynchronously(asy, FSharpOption.None, FSharpOption.None); 87 | Assert.AreEqual(123, result); 88 | } 89 | 90 | private static ValueTask DelayAndReturnAsyncByValueTask() 91 | { 92 | return new ValueTask(DelayAndReturnAsync()); 93 | } 94 | 95 | [Test] 96 | public void ValueTaskTAsAsyncTest() 97 | { 98 | var task = DelayAndReturnAsyncByValueTask(); 99 | var asy = task.AsAsync(); 100 | 101 | // MSTest not supported FSharpAsync based tests, so run synchronously here. 102 | var result = FSharpAsync.RunSynchronously(asy, FSharpOption.None, FSharpOption.None); 103 | Assert.AreEqual(123, result); 104 | } 105 | 106 | [Test] 107 | public void ConfiguredAsyncAwaiterTValueTaskAsAsyncTest() 108 | { 109 | var task = DelayAndReturnAsyncByValueTask(); 110 | var asy = task.AsAsyncConfigured(false); 111 | 112 | // MSTest not supported FSharpAsync based tests, so run synchronously here. 113 | var result = FSharpAsync.RunSynchronously(asy, FSharpOption.None, FSharpOption.None); 114 | Assert.AreEqual(123, result); 115 | } 116 | #endregion 117 | 118 | #region FSharpAsync<'T>.AsTask 119 | [Test] 120 | public async Task AsyncAsTaskTestAsync() 121 | { 122 | var asy = FSharpAsync.Sleep(500); 123 | await asy.AsTask(); 124 | } 125 | 126 | [Test] 127 | public async Task AsyncTAsTaskTestAsync() 128 | { 129 | // C# cannot create FSharpAsync<'T>, so create C#'ed Task and convert to FSharpAsync<'T>. 130 | var task = DelayAndReturnAsync(); 131 | var asy = task.AsAsync(); 132 | var result = await asy.AsTask(); 133 | 134 | Assert.AreEqual(123, result); 135 | } 136 | 137 | [Test] 138 | public void AsyncAsTaskWithCancellationTestAsync() 139 | { 140 | var cts = new CancellationTokenSource(); 141 | 142 | var asy = FSharpAsync.Sleep(500); 143 | var outerTask = asy.AsTask(cts.Token); 144 | 145 | // Force hard wait. 146 | Thread.Sleep(100); 147 | 148 | // Task cancelled. 149 | cts.Cancel(); 150 | 151 | // Continuation point. (Will raise exception) 152 | Assert.ThrowsAsync(() => outerTask); 153 | } 154 | 155 | private static async Task DelayAndReturnAsync(CancellationToken token) 156 | { 157 | await Task.Delay(500, token); /* explicitly */ 158 | return 123; 159 | } 160 | 161 | [Test] 162 | public void AsyncTAsTaskWithCancellationTestAsync() 163 | { 164 | // Information: 165 | // F#'s CancellationToken is managing contextual token in async computation expression. 166 | // But .NET CancellationToken is non contextual/default token, in C# too. 167 | // So no automatic implicit derive tokens, token just explicit set to any async operation: 168 | // Task.Delay(int, token) <-- DelayAndReturnAsync(token) <-- cts.Token 169 | 170 | var cts = new CancellationTokenSource(); 171 | 172 | // C# cannot create FSharpAsync<'T>, so create C#'ed Task and convert to FSharpAsync<'T>. 173 | var task = DelayAndReturnAsync(/* explicitly */ cts.Token); 174 | var asy = task.AsAsync(); 175 | var outerTask = asy.AsTask(cts.Token); /* explicitly, but not derived into DelayAndReturnAsync() */ 176 | 177 | // Force hard wait. 178 | Thread.Sleep(100); 179 | 180 | // Task cancelled. 181 | cts.Cancel(); 182 | 183 | // Continuation point. (Will raise exception) 184 | Assert.ThrowsAsync(() => outerTask); 185 | } 186 | 187 | private static ValueTask DelayAndReturnAsyncByValueTask(CancellationToken token) 188 | { 189 | return new ValueTask(DelayAndReturnAsync(token)); 190 | } 191 | 192 | [Test] 193 | public void AsyncTValueTaskAsTaskWithCancellationTestAsync() 194 | { 195 | // Information: 196 | // F#'s CancellationToken is managing contextual token in async computation expression. 197 | // But .NET CancellationToken is non contextual/default token, in C# too. 198 | // So no automatic implicit derive tokens, token just explicit set to any async operation: 199 | // Task.Delay(int, token) <-- DelayAndReturnAsync(token) <-- cts.Token 200 | 201 | var cts = new CancellationTokenSource(); 202 | 203 | // C# cannot create FSharpAsync<'T>, so create C#'ed Task and convert to FSharpAsync<'T>. 204 | var task = DelayAndReturnAsyncByValueTask(/* explicitly */ cts.Token); 205 | var asy = task.AsAsync(); 206 | var outerTask = asy.AsTask(cts.Token); /* explicitly, but not derived into DelayAndReturnAsyncByValueTask() */ 207 | 208 | // Force hard wait. 209 | Thread.Sleep(100); 210 | 211 | // Task cancelled. 212 | cts.Cancel(); 213 | 214 | // Continuation point. (Will raise exception) 215 | Assert.ThrowsAsync(() => outerTask); 216 | } 217 | #endregion 218 | 219 | #region FSharpAsync<'T>.GetAwaiter 220 | [Test] 221 | public async Task AsyncGetAwaiterTestAsync() 222 | { 223 | var asy = FSharpAsync.Sleep(500); 224 | await asy; 225 | } 226 | 227 | [Test] 228 | public async Task AsyncTGetAwaiterTestAsync() 229 | { 230 | // C# cannot create FSharpAsync<'T>, so create C#'ed Task and convert to FSharpAsync<'T>. 231 | var task = DelayAndReturnAsync(); 232 | var asy = task.AsAsync(); 233 | var result = await asy; 234 | 235 | Assert.AreEqual(123, result); 236 | } 237 | #endregion 238 | 239 | [Test] 240 | public void HoldSynchContextTest() 241 | { 242 | var id1 = Thread.CurrentThread.ManagedThreadId; 243 | var context = new ThreadBoundSynchronizationContext(); 244 | SynchronizationContext.SetSynchronizationContext(context); 245 | 246 | async Task ComputationAsync() 247 | { 248 | var idStartImmediately = Thread.CurrentThread.ManagedThreadId; 249 | Assert.AreEqual(id1, idStartImmediately); 250 | 251 | await FSharpAsync.StartImmediateAsTask( 252 | FSharpAsync.Sleep(300), FSharpOption.None); 253 | var idAwaitTask = Thread.CurrentThread.ManagedThreadId; 254 | Assert.AreEqual(id1, idAwaitTask); 255 | 256 | await FSharpAsync.Sleep(300).AsTask(); 257 | var idAwaited1 = Thread.CurrentThread.ManagedThreadId; 258 | Assert.AreEqual(id1, idAwaited1); 259 | 260 | await FSharpAsync.Sleep(300).AsTask(); 261 | var idAwaited2 = Thread.CurrentThread.ManagedThreadId; 262 | Assert.AreEqual(id1, idAwaited2); 263 | } 264 | 265 | context.Run(ComputationAsync()); 266 | } 267 | 268 | [TestCase(true)] 269 | [TestCase(false)] 270 | public void ThreadBoundSynchronizationContextTest(bool capture) 271 | { 272 | var id1 = Thread.CurrentThread.ManagedThreadId; 273 | var context = new ThreadBoundSynchronizationContext(); 274 | SynchronizationContext.SetSynchronizationContext(context); 275 | 276 | async Task ComputationAsync() 277 | { 278 | var id2 = Thread.CurrentThread.ManagedThreadId; 279 | Assert.AreEqual(id1, id2); 280 | 281 | var values = new[] { 1, 2, 3, 4, 5 }; 282 | var delay = TimeSpan.FromMilliseconds(100); 283 | var results = new List(); 284 | 285 | await foreach (var value in values.DelayEachAsync(delay).ConfigureAwait(capture)) 286 | { 287 | var id3 = Thread.CurrentThread.ManagedThreadId; 288 | if (capture) Assert.AreEqual(id1, id3); 289 | else Assert.AreNotEqual(id1, id3); 290 | 291 | results.Add(value); 292 | 293 | await Task.Delay(delay); 294 | } 295 | 296 | var id4 = Thread.CurrentThread.ManagedThreadId; 297 | if (capture) Assert.AreEqual(id1, id4); 298 | else Assert.AreNotEqual(id1, id4); 299 | 300 | Assert.AreEqual(values, results); 301 | } 302 | 303 | context.Run(ComputationAsync()); 304 | } 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /FSharp.Control.FusionTasks.Tests.FS/AsyncSequneceTests.fs: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // FSharp.Control.FusionTasks - F# Async workflow <--> .NET Task easy seamless interoperability library. 4 | // Copyright (c) 2016-2021 Kouji Matsui (@kozy_kekyo, @kekyo2) 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | ///////////////////////////////////////////////////////////////////////////////////////////////// 19 | 20 | namespace FSharp.Control.FusionTasks.Tests 21 | 22 | open System 23 | open System.IO 24 | open System.Diagnostics 25 | open System.Threading 26 | open System.Threading.Tasks 27 | 28 | open NUnit.Framework 29 | 30 | #nowarn "44" 31 | 32 | module AsyncSequenceTests = 33 | 34 | //////////////////////////////////////////////////////////////////////// 35 | // IAsyncEnumerable 36 | 37 | let private delay = TimeSpan.FromMilliseconds 100.0 38 | 39 | [] 40 | let AsyncEnumerableIterationTest() = 41 | let computation = async { 42 | let values = [ 2; 5; 3; 7; 1 ] 43 | let results = new System.Collections.Generic.List() 44 | for value in values.DelayEachAsync(delay) do 45 | results.Add value 46 | do! Async.Sleep delay 47 | Assert.AreEqual(values, results) 48 | } 49 | computation.AsTask() 50 | 51 | [] 52 | let AsyncEnumerableIterationNoDelayingBodyTest() = 53 | let computation = async { 54 | let values = [ 2; 5; 3; 7; 1 ] 55 | let results = new System.Collections.Generic.List() 56 | for value in values.DelayEachAsync(delay) do 57 | results.Add value 58 | Assert.AreEqual(values, results) 59 | } 60 | computation.AsTask() 61 | 62 | [] 63 | let AsyncEnumerableSynchIterationTest() = 64 | let computation = async { 65 | let values = [ 2; 5; 3; 7; 1 ] 66 | let results = new System.Collections.Generic.List() 67 | for value in values.AsAsyncEnumerable() do 68 | results.Add value 69 | do! Async.Sleep delay 70 | Assert.AreEqual(values, results) 71 | } 72 | computation.AsTask() 73 | 74 | [] 75 | let AsyncEnumerableSynchIterationNoDelayingBodyTest() = 76 | let computation = async { 77 | let values = [ 2; 5; 3; 7; 1 ] 78 | let results = new System.Collections.Generic.List() 79 | for value in values.AsAsyncEnumerable() do 80 | results.Add value 81 | Assert.AreEqual(values, results) 82 | } 83 | computation.AsTask() 84 | 85 | [] 86 | let AsyncEnumerableIterationThrowingTest() = 87 | let computation = async { 88 | let values = [ 2; 5; 3; 7; 1 ] 89 | let results = new System.Collections.Generic.List() 90 | let exn = new Exception() 91 | let mutable index = 0 92 | try 93 | for value in values.DelayEachAsync(delay) do 94 | results.Add value 95 | index <- index + 1 96 | if index >= 3 then 97 | raise exn 98 | do! Async.Sleep delay 99 | Assert.Fail() 100 | with 101 | | exn2 -> 102 | Assert.AreSame(exn, exn2) 103 | Assert.AreEqual(values |> Seq.take 3, results) 104 | } 105 | computation.AsTask() 106 | 107 | [] 108 | let AsyncEnumerableIterationThrowingNoDelayingBodyTest() = 109 | let computation = async { 110 | let values = [ 2; 5; 3; 7; 1 ] 111 | let results = new System.Collections.Generic.List() 112 | let exn = new Exception() 113 | let mutable index = 0 114 | try 115 | for value in values.DelayEachAsync(delay) do 116 | results.Add value 117 | index <- index + 1 118 | if index >= 3 then 119 | raise exn 120 | Assert.Fail() 121 | with 122 | | exn2 -> 123 | Assert.AreSame(exn, exn2) 124 | Assert.AreEqual(values |> Seq.take 3, results) 125 | } 126 | computation.AsTask() 127 | 128 | [] 129 | let AsyncEnumerableSynchIterationThrowingTest() = 130 | let computation = async { 131 | let values = [ 2; 5; 3; 7; 1 ] 132 | let results = new System.Collections.Generic.List() 133 | let exn = new Exception() 134 | let mutable index = 0 135 | try 136 | for value in values.AsAsyncEnumerable() do 137 | results.Add value 138 | index <- index + 1 139 | if index >= 3 then 140 | raise exn 141 | do! Async.Sleep delay 142 | Assert.Fail() 143 | with 144 | | exn2 -> 145 | Assert.AreSame(exn, exn2) 146 | Assert.AreEqual(values |> Seq.take 3, results) 147 | } 148 | computation.AsTask() 149 | 150 | [] 151 | let AsyncEnumerableSynchIterationThrowingNoDelayingBodyTest() = 152 | let computation = async { 153 | let values = [ 2; 5; 3; 7; 1 ] 154 | let results = new System.Collections.Generic.List() 155 | let exn = new Exception() 156 | let mutable index = 0 157 | try 158 | for value in values.AsAsyncEnumerable() do 159 | results.Add value 160 | index <- index + 1 161 | if index >= 3 then 162 | raise exn 163 | Assert.Fail() 164 | with 165 | | exn2 -> 166 | Assert.AreSame(exn, exn2) 167 | Assert.AreEqual(values |> Seq.take 3, results) 168 | } 169 | computation.AsTask() 170 | 171 | [] 172 | let AsyncEnumerableIterationThrowingBeforeTest() = 173 | let computation = async { 174 | let values = [ 2; 5; 3; 7; 1 ] 175 | let results = new System.Collections.Generic.List() 176 | try 177 | for value in values.WillThrowBefore() do 178 | results.Add value 179 | do! Async.Sleep delay 180 | Assert.Fail() 181 | with 182 | | exn -> 183 | Assert.AreEqual("WillThrowBefore", exn.Message) 184 | Assert.AreEqual(0, results.Count) 185 | } 186 | computation.AsTask() 187 | 188 | [] 189 | let AsyncEnumerableIterationThrowingAfterTest() = 190 | let computation = async { 191 | let values = [ 2; 5; 3; 7; 1 ] 192 | let results = new System.Collections.Generic.List() 193 | try 194 | for value in values.WillThrowAfter() do 195 | results.Add value 196 | do! Async.Sleep delay 197 | Assert.Fail() 198 | with 199 | | exn -> 200 | Assert.AreEqual("WillThrowAfter", exn.Message) 201 | Assert.AreEqual(values, results) 202 | } 203 | computation.AsTask() 204 | 205 | [] 206 | let AsyncEnumerableIterationThrowingInterBeforeTest() = 207 | let computation = async { 208 | let values = [ 2; 5; 3; 7; 1 ] 209 | let results = new System.Collections.Generic.List() 210 | try 211 | for value in values.WillThrowInterBefore() do 212 | results.Add value 213 | do! Async.Sleep delay 214 | Assert.Fail() 215 | with 216 | | exn -> 217 | Assert.AreEqual("WillThrowInterBefore", exn.Message) 218 | Assert.AreEqual(0, results.Count) 219 | } 220 | computation.AsTask() 221 | 222 | [] 223 | let AsyncEnumerableIterationThrowingInterAfterTest() = 224 | let computation = async { 225 | let values = [ 2; 5; 3; 7; 1 ] 226 | let results = new System.Collections.Generic.List() 227 | try 228 | for value in values.WillThrowInterAfter() do 229 | results.Add value 230 | do! Async.Sleep delay 231 | Assert.Fail() 232 | with 233 | | exn -> 234 | Assert.AreEqual("WillThrowInterAfter", exn.Message) 235 | Assert.AreEqual(values |> Seq.take 1, results) 236 | } 237 | computation.AsTask() 238 | 239 | [] 240 | let AsyncEnumerableWillCallAsyncDisposeTest() = 241 | let computation = async { 242 | let values = [ 2; 5; 3; 7; 1 ] 243 | let results = new System.Collections.Generic.List() 244 | let mutable called = false 245 | for value in values.HookAsyncEnumerable(fun () -> 246 | called <- true 247 | new ValueTask()) do 248 | results.Add value 249 | do! Async.Sleep delay 250 | Assert.IsTrue(called) 251 | Assert.AreEqual(values, results) 252 | } 253 | computation.AsTask() 254 | 255 | [] 256 | let AsyncEnumerableWillDelayCallAsyncDisposeTest() = 257 | let computation = async { 258 | let values = [ 2; 5; 3; 7; 1 ] 259 | let results = new System.Collections.Generic.List() 260 | let mutable called = false 261 | for value in values.HookAsyncEnumerable(fun () -> 262 | new ValueTask( 263 | Async.StartImmediateAsTask (async { 264 | do! Async.Sleep delay 265 | called <- true }))) do 266 | results.Add value 267 | do! Async.Sleep delay 268 | Assert.IsTrue(called) 269 | Assert.AreEqual(values, results) 270 | } 271 | computation.AsTask() 272 | 273 | [] 274 | let AsyncEnumerableWillThrowAsyncDisposeTest() = 275 | let computation = async { 276 | let values = [ 2; 5; 3; 7; 1 ] 277 | let results = new System.Collections.Generic.List() 278 | let exn = new Exception() 279 | try 280 | for value in values.HookAsyncEnumerable(fun () -> raise exn) do 281 | results.Add value 282 | do! Async.Sleep delay 283 | Assert.Fail() 284 | with 285 | | exn2 -> 286 | Assert.AreSame(exn, exn2) 287 | Assert.AreEqual(values, results) 288 | } 289 | computation.AsTask() 290 | 291 | [] 292 | let AsyncEnumerableWillDelayedThrowAsyncDisposeTest() = 293 | let computation = async { 294 | let values = [ 2; 5; 3; 7; 1 ] 295 | let results = new System.Collections.Generic.List() 296 | let exn = new Exception() 297 | try 298 | for value in values.HookAsyncEnumerable(fun () -> 299 | new ValueTask( 300 | Async.StartImmediateAsTask (async { 301 | do! Async.Sleep delay 302 | raise exn }))) do 303 | results.Add value 304 | do! Async.Sleep delay 305 | Assert.Fail() 306 | with 307 | | exn2 -> 308 | Assert.AreSame(exn, exn2) 309 | Assert.AreEqual(values, results) 310 | } 311 | computation.AsTask() 312 | 313 | [] 314 | [] 315 | let AsyncEnumerableIterationCaptureContextTest(capture: bool) = 316 | let context = new ThreadBoundSynchronizationContext() 317 | SynchronizationContext.SetSynchronizationContext context 318 | let computation = async { 319 | let values = [ 2; 5; 3; 7; 1 ] 320 | let results = new System.Collections.Generic.List() 321 | let tid = Thread.CurrentThread.ManagedThreadId 322 | for value in values.DelayEachAsync(delay).ConfigureAwait(capture) do 323 | let tid2 = Thread.CurrentThread.ManagedThreadId 324 | if capture then 325 | Assert.AreEqual(tid, tid2) 326 | else 327 | Assert.AreNotEqual(tid, tid2) 328 | results.Add value 329 | do! Async.Sleep delay 330 | let tid3 = Thread.CurrentThread.ManagedThreadId 331 | if capture then 332 | Assert.AreEqual(tid, tid3) 333 | else 334 | Assert.AreNotEqual(tid, tid3) 335 | Assert.AreEqual(values, results) 336 | } 337 | context.Run(computation.AsTask()) 338 | 339 | //////////////////////////////////////////////////////////////////////// 340 | // IAsyncDisposable 341 | 342 | [] 343 | let AsyncDisposableTest() = 344 | let mutable index = 0 345 | let computation = async { 346 | let inner = async { 347 | use _ = AsyncDisposableFactory.CreateDelegatedAsyncDisposable(fun () -> 348 | (async { 349 | do! Async.Sleep delay 350 | index <- index + 1 351 | }).AsTask()) 352 | Assert.AreEqual(0, index) 353 | return () 354 | } 355 | do! inner 356 | Assert.AreEqual(1, index) 357 | } 358 | computation.AsTask() 359 | 360 | [] 361 | let AsyncDisposableThrowedTest() = 362 | let ex = new Exception() 363 | let computation = async { 364 | let inner = async { 365 | use _ = AsyncDisposableFactory.CreateDelegatedAsyncDisposable(fun () -> 366 | (async { 367 | do! Async.Sleep delay 368 | }).AsTask()) 369 | raise ex 370 | return () 371 | } 372 | try 373 | do! inner 374 | Assert.Fail() 375 | with 376 | | exn -> Assert.AreSame(ex, exn) 377 | } 378 | computation.AsTask() 379 | 380 | [] 381 | let AsyncDisposableThrowedDisposingTest() = 382 | let ex = new Exception() 383 | let computation = async { 384 | let inner = async { 385 | use _ = AsyncDisposableFactory.CreateDelegatedAsyncDisposable(fun () -> 386 | (async { 387 | do! Async.Sleep delay 388 | raise ex 389 | }).AsTask()) 390 | return () 391 | } 392 | try 393 | do! inner 394 | Assert.Fail() 395 | with 396 | | exn -> Assert.AreSame(ex, exn) 397 | } 398 | computation.AsTask() 399 | 400 | //[] 401 | //let AsyncDisposableCaptureContextTest() = 402 | // In currently C#, lacks a feature for detaching synch context doing at just after asynchronous disposer. 403 | // https://github.com/dotnet/csharplang/discussions/2661 404 | -------------------------------------------------------------------------------- /FSharp.Control.FusionTasks.Tests.FS/BasisTests.fs: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // FSharp.Control.FusionTasks - F# Async workflow <--> .NET Task easy seamless interoperability library. 4 | // Copyright (c) 2016-2021 Kouji Matsui (@kozy_kekyo, @kekyo2) 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | ///////////////////////////////////////////////////////////////////////////////////////////////// 19 | 20 | namespace FSharp.Control.FusionTasks.Tests 21 | 22 | open System 23 | open System.IO 24 | open System.Diagnostics 25 | open System.Threading 26 | open System.Threading.Tasks 27 | 28 | open NUnit.Framework 29 | 30 | #nowarn "44" 31 | 32 | module BasisTests = 33 | 34 | //////////////////////////////////////////////////////////////////////// 35 | // Basis 36 | 37 | [] 38 | let AsyncBuilderAsAsyncFromTaskTest() = 39 | let test = async { 40 | let r = Random() 41 | let data = Seq.init 100000 (fun i -> 0uy) |> Seq.toArray 42 | do r.NextBytes data 43 | use ms = new MemoryStream() 44 | let computation = async { 45 | do! ms.WriteAsync(data, 0, data.Length) 46 | Assert.AreEqual(data, ms.ToArray()) 47 | } 48 | do! computation 49 | } 50 | test.AsTask() 51 | 52 | [] 53 | let AsyncBuilderAsAsyncFromTaskTTest() = 54 | let test = async { 55 | let r = Random() 56 | let data = Seq.init 100000 (fun i -> 0uy) |> Seq.toArray 57 | do r.NextBytes data 58 | let computation = async { 59 | use ms = new MemoryStream() 60 | do ms.Write(data, 0, data.Length) 61 | do ms.Position <- 0L 62 | let! length = ms.ReadAsync(data, 0, data.Length) 63 | Assert.AreEqual(data.Length, length) 64 | 65 | return ms.ToArray() 66 | } 67 | let! results = computation 68 | Assert.AreEqual(data, results) 69 | } 70 | test.AsTask() 71 | 72 | [] 73 | let AsyncBuilderAsAsyncFromValueTaskTest() = 74 | let test = async { 75 | let r = Random() 76 | let data = Seq.init 100000 (fun i -> 0uy) |> Seq.toArray 77 | do r.NextBytes data 78 | use ms = new MemoryStream() 79 | let computation = async { 80 | do! new ValueTask(ms.WriteAsync(data, 0, data.Length)) 81 | } 82 | do! computation 83 | Assert.AreEqual(data, ms.ToArray()) 84 | } 85 | test.AsTask() 86 | 87 | [] 88 | let AsyncBuilderAsAsyncFromValueTaskTTest() = 89 | let test = async { 90 | let r = Random() 91 | let data = Seq.init 100000 (fun i -> 0uy) |> Seq.toArray 92 | do r.NextBytes data 93 | let computation = async { 94 | use ms = new MemoryStream() 95 | do ms.Write(data, 0, data.Length) 96 | do ms.Position <- 0L 97 | let! length = new ValueTask(ms.ReadAsync(data, 0, data.Length)) 98 | Assert.AreEqual(data.Length, length) 99 | 100 | return ms.ToArray() 101 | } 102 | let! results = computation 103 | Assert.AreEqual(data, results) 104 | } 105 | test.AsTask() 106 | 107 | [] 108 | let AsyncBuilderAsAsyncVTest() = 109 | let test = async { 110 | let r = Random() 111 | let data = Seq.init 100000 (fun i -> 0uy) |> Seq.toArray 112 | do r.NextBytes data 113 | let computation = async { 114 | use ms = new MemoryStream() 115 | do! new ValueTask(ms.WriteAsync(data, 0, data.Length)) 116 | do ms.Position <- 0L 117 | let length = ms.Read(data, 0, data.Length) 118 | Assert.AreEqual(data.Length, length) 119 | 120 | return ms.ToArray() 121 | } 122 | let! results = computation 123 | Assert.AreEqual(data, results) 124 | } 125 | test.AsTask() 126 | 127 | [] 128 | let AsyncBuilderAsAsyncVTTest() = 129 | let test = async { 130 | let r = Random() 131 | let data = Seq.init 100000 (fun i -> 0uy) |> Seq.toArray 132 | do r.NextBytes data 133 | let computation = async { 134 | use ms = new MemoryStream() 135 | do ms.Write(data, 0, data.Length) 136 | do ms.Position <- 0L 137 | let! length = new ValueTask(ms.ReadAsync(data, 0, data.Length)) 138 | Assert.AreEqual(data.Length, length) 139 | return ms.ToArray() 140 | } 141 | let! results = computation 142 | Assert.AreEqual(data, results) 143 | } 144 | test.AsTask() 145 | 146 | [] 147 | let AsyncBuilderWithAsyncAndTaskCombinationTest() = 148 | let asyncGenData() = async { 149 | let r = Random() 150 | let data = Seq.init 100000 (fun i -> 0uy) |> Seq.toArray 151 | do r.NextBytes data 152 | return data 153 | } 154 | let computation = async { 155 | let! data = asyncGenData() 156 | use ms = new MemoryStream() 157 | do! ms.WriteAsync(data, 0, data.Length) 158 | Assert.AreEqual(data, ms.ToArray()) 159 | } 160 | computation.AsTask() 161 | 162 | [] 163 | let AsyncBuilderCompilesForInTest() = 164 | let computation = async { 165 | let mutable result = 0 166 | for i in {1..10} do 167 | result <- result + i 168 | Assert.AreEqual(55, result) 169 | } 170 | 171 | computation.AsTask() 172 | 173 | // #11 174 | //[] 175 | //[] 176 | //let AsyncBuilderCompilesIfInTest(flag, expected) = 177 | // let computation = async { 178 | // if flag 179 | // then 180 | // Assert.IsTrue flag 181 | // return () 182 | // do! Task.Delay 100 183 | // Assert.IsFalse flag 184 | // } 185 | // computation |> Async.RunSynchronously 186 | 187 | //////////////////////////////////////////////////////////////////////// 188 | // SynchronizationContext 189 | 190 | [] 191 | let HoldSynchContextTest() = 192 | let id1 = Thread.CurrentThread.ManagedThreadId 193 | let context = new ThreadBoundSynchronizationContext() 194 | SynchronizationContext.SetSynchronizationContext(context) 195 | let computation = async { 196 | let idStartImmediately = Thread.CurrentThread.ManagedThreadId 197 | Assert.AreEqual(id1, idStartImmediately) 198 | 199 | do! Task.Delay 300 |> Async.AwaitTask 200 | let idAwaitTask = Thread.CurrentThread.ManagedThreadId 201 | Assert.AreEqual(id1, idAwaitTask) 202 | 203 | do! Task.Delay 300 204 | let idAwaited1 = Thread.CurrentThread.ManagedThreadId 205 | Assert.AreEqual(id1, idAwaited1) 206 | 207 | do! Task.Delay 300 208 | let idAwaited2 = Thread.CurrentThread.ManagedThreadId 209 | Assert.AreEqual(id1, idAwaited2) 210 | } 211 | context.Run(computation.AsTask()) 212 | -------------------------------------------------------------------------------- /FSharp.Control.FusionTasks.Tests.FS/ConfiguredTests.fs: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // FSharp.Control.FusionTasks - F# Async workflow <--> .NET Task easy seamless interoperability library. 4 | // Copyright (c) 2016-2021 Kouji Matsui (@kozy_kekyo, @kekyo2) 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | ///////////////////////////////////////////////////////////////////////////////////////////////// 19 | 20 | namespace FSharp.Control.FusionTasks.Tests 21 | 22 | open System 23 | open System.IO 24 | open System.Diagnostics 25 | open System.Threading 26 | open System.Threading.Tasks 27 | 28 | open NUnit.Framework 29 | 30 | #nowarn "44" 31 | 32 | module ConfiguredTests = 33 | 34 | //////////////////////////////////////////////////////////////////////// 35 | // Configured async monad. 36 | 37 | [] 38 | let AsyncBuilderAsAsyncCTTest() = 39 | let test = async { 40 | let r = Random() 41 | let data = Seq.init 100000 (fun i -> 0uy) |> Seq.toArray 42 | do r.NextBytes data 43 | use ms = new MemoryStream() 44 | let computation = async { 45 | do! ms.WriteAsync(data, 0, data.Length).ConfigureAwait(false) 46 | } 47 | do! computation 48 | Assert.AreEqual(data, ms.ToArray()) 49 | } 50 | test.AsTask() 51 | 52 | [] 53 | let AsyncBuilderAsAsyncCTTTest() = 54 | let test = async { 55 | let r = Random() 56 | let data = Seq.init 100000 (fun i -> 0uy) |> Seq.toArray 57 | do r.NextBytes data 58 | let computation = async { 59 | use ms = new MemoryStream() 60 | do ms.Write(data, 0, data.Length) 61 | do ms.Position <- 0L 62 | let! length = ms.ReadAsync(data, 0, data.Length).ConfigureAwait(false) 63 | Assert.AreEqual(data.Length, length) 64 | 65 | return ms.ToArray() 66 | } 67 | let! results = computation 68 | Assert.AreEqual(data, results) 69 | } 70 | test.AsTask() 71 | 72 | [] 73 | let AsyncBuilderAsAsyncCVTTest() = 74 | let test = async { 75 | let r = Random() 76 | let data = Seq.init 100000 (fun i -> 0uy) |> Seq.toArray 77 | do r.NextBytes data 78 | use ms = new MemoryStream() 79 | let computation = async { 80 | do! (new ValueTask(ms.WriteAsync(data, 0, data.Length))).ConfigureAwait(false) 81 | } 82 | do! computation 83 | Assert.AreEqual(data, ms.ToArray()) 84 | } 85 | test.AsTask() 86 | 87 | [] 88 | let AsyncBuilderAsAsyncCVTTTest() = 89 | let test = async { 90 | let r = Random() 91 | let data = Seq.init 100000 (fun i -> 0uy) |> Seq.toArray 92 | do r.NextBytes data 93 | let computation = async { 94 | use ms = new MemoryStream() 95 | do ms.Write(data, 0, data.Length) 96 | do ms.Position <- 0L 97 | let! length = (new ValueTask(ms.ReadAsync(data, 0, data.Length))).ConfigureAwait(false) 98 | Assert.AreEqual(data.Length, length) 99 | 100 | return ms.ToArray() 101 | } 102 | let! results = computation 103 | Assert.AreEqual(data, results) 104 | } 105 | test.AsTask() 106 | 107 | [] 108 | [] 109 | let AsyncBuilderAsAsyncCTATest() = 110 | let test = async { 111 | let r = Random() 112 | let data = Seq.init 100000 (fun i -> 0uy) |> Seq.toArray 113 | do r.NextBytes data 114 | use ms = new MemoryStream() 115 | let computation = async { 116 | do! ms.WriteAsync(data, 0, data.Length).AsyncConfigure(false) 117 | } 118 | do! computation 119 | Assert.AreEqual(data, ms.ToArray()) 120 | } 121 | test.AsTask() 122 | 123 | [] 124 | [] 125 | let AsyncBuilderAsAsyncCTATTest() = 126 | let test = async { 127 | let r = Random() 128 | let data = Seq.init 100000 (fun i -> 0uy) |> Seq.toArray 129 | do r.NextBytes data 130 | let computation = async { 131 | use ms = new MemoryStream() 132 | do ms.Write(data, 0, data.Length) 133 | do ms.Position <- 0L 134 | let! length = ms.ReadAsync(data, 0, data.Length).AsyncConfigure(false) 135 | Assert.AreEqual(data.Length, length) 136 | 137 | return ms.ToArray() 138 | } 139 | let! results = computation 140 | Assert.AreEqual(data, results) 141 | } 142 | test.AsTask() 143 | 144 | [] 145 | [] 146 | let AsyncBuilderAsAsyncCVTATest() = 147 | let test = async { 148 | let r = Random() 149 | let data = Seq.init 100000 (fun i -> 0uy) |> Seq.toArray 150 | do r.NextBytes data 151 | use ms = new MemoryStream() 152 | let computation = async { 153 | do! (new ValueTask(ms.WriteAsync(data, 0, data.Length))).AsyncConfigure(false) 154 | } 155 | do! computation 156 | Assert.AreEqual(data, ms.ToArray()) 157 | } 158 | test.AsTask() 159 | 160 | [] 161 | [] 162 | let AsyncBuilderAsAsyncCVTATTest() = 163 | let test = async { 164 | let r = Random() 165 | let data = Seq.init 100000 (fun i -> 0uy) |> Seq.toArray 166 | do r.NextBytes data 167 | let computation = async { 168 | use ms = new MemoryStream() 169 | do ms.Write(data, 0, data.Length) 170 | do ms.Position <- 0L 171 | let! length = (new ValueTask(ms.ReadAsync(data, 0, data.Length))).AsyncConfigure(false) 172 | Assert.AreEqual(data.Length, length) 173 | 174 | return ms.ToArray() 175 | } 176 | let! results = computation 177 | Assert.AreEqual(data, results) 178 | } 179 | test.AsTask() 180 | -------------------------------------------------------------------------------- /FSharp.Control.FusionTasks.Tests.FS/FSharp.Control.FusionTasks.Tests.FS.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net48;netcoreapp2.2;netcoreapp3.1;net5.0-windows;net6.0 5 | 988;NU1803 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /FSharp.Control.FusionTasks.Tests.FS/OperatorTests.fs: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // FSharp.Control.FusionTasks - F# Async workflow <--> .NET Task easy seamless interoperability library. 4 | // Copyright (c) 2016-2021 Kouji Matsui (@kozy_kekyo, @kekyo2) 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | ///////////////////////////////////////////////////////////////////////////////////////////////// 19 | 20 | namespace FSharp.Control.FusionTasks.Tests 21 | 22 | open System 23 | open System.IO 24 | open System.Diagnostics 25 | open System.Threading 26 | open System.Threading.Tasks 27 | 28 | open NUnit.Framework 29 | 30 | #nowarn "44" 31 | 32 | module OperatorTests = 33 | 34 | //////////////////////////////////////////////////////////////////////// 35 | // Explicitly operators 36 | 37 | [] 38 | let OperatorAsAsyncFromTaskTest() = 39 | let test = async { 40 | let r = Random() 41 | let data = Seq.init 100000 (fun i -> 0uy) |> Seq.toArray 42 | do r.NextBytes data 43 | use ms = new MemoryStream() 44 | let computation = async { 45 | do! ms.WriteAsync(data, 0, data.Length) |> Async.AsAsync 46 | Assert.AreEqual(data, ms.ToArray()) 47 | } 48 | do! computation 49 | } 50 | test.AsTask() 51 | 52 | [] 53 | let OperatorAsAsyncFromTaskTTest() = 54 | let test = async { 55 | let r = Random() 56 | let data = Seq.init 100000 (fun i -> 0uy) |> Seq.toArray 57 | do r.NextBytes data 58 | let computation = async { 59 | use ms = new MemoryStream() 60 | do ms.Write(data, 0, data.Length) 61 | do ms.Position <- 0L 62 | let! length = ms.ReadAsync(data, 0, data.Length) |> Async.AsAsync 63 | Assert.AreEqual(data.Length, length) 64 | 65 | return ms.ToArray() 66 | } 67 | let! results = computation 68 | Assert.AreEqual(data, results) 69 | } 70 | test.AsTask() 71 | 72 | [] 73 | let OperatorAsAsyncFromValueTaskTest() = 74 | let test = async { 75 | let r = Random() 76 | let data = Seq.init 100000 (fun i -> 0uy) |> Seq.toArray 77 | do r.NextBytes data 78 | use ms = new MemoryStream() 79 | let computation = async { 80 | do! new ValueTask(ms.WriteAsync(data, 0, data.Length)) |> Async.AsAsync 81 | Assert.AreEqual(data, ms.ToArray()) 82 | } 83 | do! computation 84 | } 85 | test.AsTask() 86 | 87 | [] 88 | let OperatorAsAsyncFromValueTaskTTest() = 89 | let test = async { 90 | let r = Random() 91 | let data = Seq.init 100000 (fun i -> 0uy) |> Seq.toArray 92 | do r.NextBytes data 93 | let computation = async { 94 | use ms = new MemoryStream() 95 | do ms.Write(data, 0, data.Length) 96 | do ms.Position <- 0L 97 | let! length = new ValueTask(ms.ReadAsync(data, 0, data.Length)) |> Async.AsAsync 98 | Assert.AreEqual(data.Length, length) 99 | 100 | return ms.ToArray() 101 | } 102 | let! results = computation 103 | Assert.AreEqual(data, results) 104 | } 105 | test.AsTask() 106 | 107 | [] 108 | let OperatorAsAsyncFromTaskConfiguredTest() = 109 | let test = async { 110 | let r = Random() 111 | let data = Seq.init 100000 (fun i -> 0uy) |> Seq.toArray 112 | do r.NextBytes data 113 | use ms = new MemoryStream() 114 | let computation = async { 115 | do! ms.WriteAsync(data, 0, data.Length).ConfigureAwait(false) |> Async.AsAsync 116 | Assert.AreEqual(data, ms.ToArray()) 117 | } 118 | do! computation 119 | } 120 | test.AsTask() 121 | 122 | 123 | [] 124 | let OperatorAsAsyncFromTaskTConfiguredTest() = 125 | let test = async { 126 | let r = Random() 127 | let data = Seq.init 100000 (fun i -> 0uy) |> Seq.toArray 128 | do r.NextBytes data 129 | let computation = async { 130 | use ms = new MemoryStream() 131 | do ms.Write(data, 0, data.Length) 132 | do ms.Position <- 0L 133 | let! length = ms.ReadAsync(data, 0, data.Length).ConfigureAwait(false) |> Async.AsAsync 134 | Assert.AreEqual(data.Length, length) 135 | 136 | return ms.ToArray() 137 | } 138 | let! results = computation 139 | Assert.AreEqual(data, results) 140 | } 141 | test.AsTask() 142 | 143 | [] 144 | let OperatorAsAsyncFromValueTaskConfiguredTest() = 145 | let test = async { 146 | let r = Random() 147 | let data = Seq.init 100000 (fun i -> 0uy) |> Seq.toArray 148 | do r.NextBytes data 149 | use ms = new MemoryStream() 150 | let computation = async { 151 | do! (new ValueTask(ms.WriteAsync(data, 0, data.Length))).ConfigureAwait(false) |> Async.AsAsync 152 | Assert.AreEqual(data, ms.ToArray()) 153 | } 154 | do! computation 155 | } 156 | test.AsTask() 157 | 158 | 159 | [] 160 | let OperatorAsAsyncFromValueTaskTConfiguredTest() = 161 | let test = async { 162 | let r = Random() 163 | let data = Seq.init 100000 (fun i -> 0uy) |> Seq.toArray 164 | do r.NextBytes data 165 | let computation = async { 166 | use ms = new MemoryStream() 167 | do ms.Write(data, 0, data.Length) 168 | do ms.Position <- 0L 169 | let! length = (new ValueTask(ms.ReadAsync(data, 0, data.Length))).ConfigureAwait(false) |> Async.AsAsync 170 | Assert.AreEqual(data.Length, length) 171 | 172 | return ms.ToArray() 173 | } 174 | let! results = computation 175 | Assert.AreEqual(data, results) 176 | } 177 | test.AsTask() 178 | -------------------------------------------------------------------------------- /FSharp.Control.FusionTasks.Tests.Utilities/AsyncDisposableFactory.cs: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // FSharp.Control.FusionTasks - F# Async workflow <--> .NET Task easy seamless interoperability library. 4 | // Copyright (c) 2016-2021 Kouji Matsui (@kozy_kekyo, @kekyo2) 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | ///////////////////////////////////////////////////////////////////////////////////////////////// 19 | 20 | using System; 21 | using System.Threading.Tasks; 22 | 23 | namespace FSharp.Control.FusionTasks.Tests 24 | { 25 | public static class AsyncDisposableFactory 26 | { 27 | public static IAsyncDisposable CreateDelegatedAsyncDisposable(Func disposeAsync) => 28 | new DelegatedAsyncDisposable(disposeAsync); 29 | 30 | private sealed class DelegatedAsyncDisposable : IAsyncDisposable 31 | { 32 | private readonly Func disposeAsync; 33 | 34 | public DelegatedAsyncDisposable(Func disposeAsync) => 35 | this.disposeAsync = disposeAsync; 36 | 37 | public ValueTask DisposeAsync() => 38 | new ValueTask(this.disposeAsync()); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /FSharp.Control.FusionTasks.Tests.Utilities/AsyncEnumerableFactory.cs: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // FSharp.Control.FusionTasks - F# Async workflow <--> .NET Task easy seamless interoperability library. 4 | // Copyright (c) 2016-2021 Kouji Matsui (@kozy_kekyo, @kekyo2) 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | ///////////////////////////////////////////////////////////////////////////////////////////////// 19 | 20 | using System; 21 | using System.Collections.Generic; 22 | using System.Threading; 23 | using System.Threading.Tasks; 24 | 25 | #pragma warning disable CS1998 26 | #pragma warning disable CS0162 27 | 28 | namespace FSharp.Control.FusionTasks.Tests 29 | { 30 | public static class AsyncEnumerableFactory 31 | { 32 | public static async IAsyncEnumerable DelayEachAsync( 33 | this IEnumerable enumerable, 34 | TimeSpan delay) 35 | { 36 | foreach (var value in enumerable) 37 | { 38 | await Task.Delay(delay).ConfigureAwait(false); 39 | yield return value; 40 | } 41 | } 42 | 43 | public static async IAsyncEnumerable AsAsyncEnumerable( 44 | this IEnumerable enumerable) 45 | { 46 | foreach (var value in enumerable) 47 | { 48 | yield return value; 49 | } 50 | } 51 | 52 | public static async IAsyncEnumerable WillThrowBefore( 53 | this IEnumerable enumerable) 54 | { 55 | throw new Exception("WillThrowBefore"); 56 | foreach (var value in enumerable) 57 | { 58 | yield return value; 59 | } 60 | } 61 | 62 | public static async IAsyncEnumerable WillThrowAfter( 63 | this IEnumerable enumerable) 64 | { 65 | foreach (var value in enumerable) 66 | { 67 | yield return value; 68 | } 69 | throw new Exception("WillThrowAfter"); 70 | } 71 | 72 | public static async IAsyncEnumerable WillThrowInterBefore( 73 | this IEnumerable enumerable) 74 | { 75 | foreach (var value in enumerable) 76 | { 77 | throw new Exception("WillThrowInterBefore"); 78 | yield return value; 79 | } 80 | } 81 | 82 | public static async IAsyncEnumerable WillThrowInterAfter( 83 | this IEnumerable enumerable) 84 | { 85 | foreach (var value in enumerable) 86 | { 87 | yield return value; 88 | throw new Exception("WillThrowInterAfter"); 89 | } 90 | } 91 | 92 | public static IAsyncEnumerable HookAsyncEnumerable( 93 | this IEnumerable enumerable, Func dispose) => 94 | new AsyncEnumerable( 95 | enumerable, 96 | enumerator => enumerator.MoveNext(), 97 | enumerator => enumerator.Current, 98 | dispose); 99 | 100 | private sealed class AsyncEnumerable : IAsyncEnumerable 101 | { 102 | private readonly IEnumerable enumerable; 103 | private readonly Func, bool> moveNext; 104 | private readonly Func, T> current; 105 | private readonly Func dispose; 106 | 107 | public AsyncEnumerable( 108 | IEnumerable enumerable, 109 | Func, bool> moveNext, 110 | Func, T> current, 111 | Func dispose) 112 | { 113 | this.enumerable = enumerable; 114 | this.moveNext = moveNext; 115 | this.current = current; 116 | this.dispose = dispose; 117 | } 118 | 119 | public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) => 120 | new AsyncEnumerator( 121 | this.enumerable.GetEnumerator(), 122 | this.moveNext, 123 | this.current, 124 | this.dispose); 125 | } 126 | 127 | private sealed class AsyncEnumerator : IAsyncEnumerator 128 | { 129 | private readonly IEnumerator enumerator; 130 | private readonly Func, bool> moveNext; 131 | private readonly Func, T> current; 132 | private readonly Func dispose; 133 | 134 | public AsyncEnumerator( 135 | IEnumerator enumerator, 136 | Func, bool> moveNext, 137 | Func, T> current, 138 | Func dispose) 139 | { 140 | this.enumerator = enumerator; 141 | this.moveNext = moveNext; 142 | this.current = current; 143 | this.dispose = dispose; 144 | } 145 | 146 | public T Current => 147 | this.current(this.enumerator); 148 | 149 | public ValueTask MoveNextAsync() => 150 | new ValueTask(this.moveNext(this.enumerator)); 151 | 152 | public ValueTask DisposeAsync() => 153 | this.dispose(); 154 | } 155 | } 156 | } -------------------------------------------------------------------------------- /FSharp.Control.FusionTasks.Tests.Utilities/FSharp.Control.FusionTasks.Tests.Utilities.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net48;netstandard2.0;netstandard2.1;net5.0;net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /FSharp.Control.FusionTasks.Tests.Utilities/ThreadBoundSynchronizationContext.cs: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // FSharp.Control.FusionTasks - F# Async workflow <--> .NET Task easy seamless interoperability library. 4 | // Copyright (c) 2016-2021 Kouji Matsui (@kozy_kekyo, @kekyo2) 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | ///////////////////////////////////////////////////////////////////////////////////////////////// 19 | 20 | using System; 21 | using System.Collections.Generic; 22 | using System.Threading; 23 | using System.Threading.Tasks; 24 | 25 | namespace FSharp.Control.FusionTasks.Tests 26 | { 27 | // https://github.com/kekyo/SynchContextSample 28 | public sealed class ThreadBoundSynchronizationContext : 29 | SynchronizationContext 30 | { 31 | private readonly struct Entry 32 | { 33 | public readonly SendOrPostCallback Callback; 34 | public readonly object? State; 35 | 36 | public Entry(SendOrPostCallback callback, object? state) 37 | { 38 | this.Callback = callback; 39 | this.State = state; 40 | } 41 | } 42 | 43 | private readonly Queue entries = new Queue(); 44 | private int? boundThreadId; 45 | private readonly ManualResetEventSlim queued = new ManualResetEventSlim(false); 46 | private readonly ManualResetEventSlim quit = new ManualResetEventSlim(false); 47 | 48 | public void Run(Task? runTask) 49 | { 50 | this.boundThreadId = Thread.CurrentThread.ManagedThreadId; 51 | 52 | runTask?.ContinueWith(task => 53 | { 54 | if (task.IsFaulted || task.IsCanceled) 55 | { 56 | this.Post(_ => task.Wait(), null); 57 | } 58 | else 59 | { 60 | this.Quit(); 61 | } 62 | }); 63 | 64 | while (true) 65 | { 66 | if (WaitHandle.WaitAny(new [] { this.queued.WaitHandle, this.quit.WaitHandle }) == 1) 67 | { 68 | break; 69 | } 70 | 71 | loop: 72 | Entry entry; 73 | lock (this.entries) 74 | { 75 | if (this.entries.Count == 0) 76 | { 77 | this.queued.Reset(); 78 | continue; 79 | } 80 | entry = this.entries.Dequeue(); 81 | } 82 | 83 | entry.Callback(entry.State); 84 | goto loop; 85 | } 86 | } 87 | 88 | public void Quit() => 89 | this.quit.Set(); 90 | 91 | public override void Send(SendOrPostCallback d, object? state) 92 | { 93 | lock (this.entries) 94 | { 95 | if (this.boundThreadId != Thread.CurrentThread.ManagedThreadId) 96 | { 97 | this.entries.Enqueue(new Entry(d, state)); 98 | if (this.entries.Count >= 1) 99 | { 100 | this.queued.Set(); 101 | } 102 | return; 103 | } 104 | } 105 | 106 | d(state); 107 | } 108 | 109 | public override void Post(SendOrPostCallback d, object? state) 110 | { 111 | lock (this.entries) 112 | { 113 | this.entries.Enqueue(new Entry(d, state)); 114 | if (this.entries.Count >= 1) 115 | { 116 | this.queued.Set(); 117 | } 118 | } 119 | } 120 | } 121 | } -------------------------------------------------------------------------------- /FSharp.Control.FusionTasks.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32901.215 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{BEB599F7-8021-4E01-9200-B9ABF1D9D623}" 7 | ProjectSection(SolutionItems) = preProject 8 | .gitignore = .gitignore 9 | build-nupkg.bat = build-nupkg.bat 10 | build-nupkg.sh = build-nupkg.sh 11 | .github\workflows\build.yml = .github\workflows\build.yml 12 | Directory.Build.props = Directory.Build.props 13 | FSharp.Control.FusionTasks.sln.licenseheader = FSharp.Control.FusionTasks.sln.licenseheader 14 | LICENSE.txt = LICENSE.txt 15 | NuGet.Config = NuGet.Config 16 | README.md = README.md 17 | EndProjectSection 18 | EndProject 19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Images", "Images", "{5EA37107-7C53-4291-8EAD-543B392CE2B1}" 20 | ProjectSection(SolutionItems) = preProject 21 | Images\FSharp.Control.FusionTasks.100.png = Images\FSharp.Control.FusionTasks.100.png 22 | Images\FSharp.Control.FusionTasks.128.png = Images\FSharp.Control.FusionTasks.128.png 23 | Images\FSharp.Control.FusionTasks.design = Images\FSharp.Control.FusionTasks.design 24 | EndProjectSection 25 | EndProject 26 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Control.FusionTasks", "FSharp.Control.FusionTasks\FSharp.Control.FusionTasks.fsproj", "{43D74C62-B635-447E-B99B-7D3A588B093F}" 27 | EndProject 28 | Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharp.Control.FusionTasks.Tests.FS", "FSharp.Control.FusionTasks.Tests.FS\FSharp.Control.FusionTasks.Tests.FS.fsproj", "{053C93E0-E0F1-41FB-9F29-9D9877C685DF}" 29 | EndProject 30 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FSharp.Control.FusionTasks.Tests.CS", "FSharp.Control.FusionTasks.Tests.CS\FSharp.Control.FusionTasks.Tests.CS.csproj", "{46EC46A3-A601-4B04-9CB0-0113D01E8F8A}" 31 | EndProject 32 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FSharp.Control.FusionTasks.Tests.Utilities", "FSharp.Control.FusionTasks.Tests.Utilities\FSharp.Control.FusionTasks.Tests.Utilities.csproj", "{3C19BE31-9D89-4AA9-BA88-30AC4DFEBAC0}" 33 | EndProject 34 | Global 35 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 36 | Debug|Any CPU = Debug|Any CPU 37 | Release|Any CPU = Release|Any CPU 38 | EndGlobalSection 39 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 40 | {43D74C62-B635-447E-B99B-7D3A588B093F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {43D74C62-B635-447E-B99B-7D3A588B093F}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {43D74C62-B635-447E-B99B-7D3A588B093F}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {43D74C62-B635-447E-B99B-7D3A588B093F}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {053C93E0-E0F1-41FB-9F29-9D9877C685DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {053C93E0-E0F1-41FB-9F29-9D9877C685DF}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {053C93E0-E0F1-41FB-9F29-9D9877C685DF}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {053C93E0-E0F1-41FB-9F29-9D9877C685DF}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {46EC46A3-A601-4B04-9CB0-0113D01E8F8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {46EC46A3-A601-4B04-9CB0-0113D01E8F8A}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {46EC46A3-A601-4B04-9CB0-0113D01E8F8A}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {46EC46A3-A601-4B04-9CB0-0113D01E8F8A}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {3C19BE31-9D89-4AA9-BA88-30AC4DFEBAC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {3C19BE31-9D89-4AA9-BA88-30AC4DFEBAC0}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {3C19BE31-9D89-4AA9-BA88-30AC4DFEBAC0}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {3C19BE31-9D89-4AA9-BA88-30AC4DFEBAC0}.Release|Any CPU.Build.0 = Release|Any CPU 56 | EndGlobalSection 57 | GlobalSection(SolutionProperties) = preSolution 58 | HideSolutionNode = FALSE 59 | EndGlobalSection 60 | GlobalSection(NestedProjects) = preSolution 61 | {5EA37107-7C53-4291-8EAD-543B392CE2B1} = {BEB599F7-8021-4E01-9200-B9ABF1D9D623} 62 | EndGlobalSection 63 | GlobalSection(ExtensibilityGlobals) = postSolution 64 | SolutionGuid = {0FB2B9EE-D327-40E1-BF78-8D8F51B37A1A} 65 | EndGlobalSection 66 | EndGlobal 67 | -------------------------------------------------------------------------------- /FSharp.Control.FusionTasks.sln.licenseheader: -------------------------------------------------------------------------------- 1 | extensions: designer.cs generated.cs 2 | extensions: .cs .fs .cpp .h 3 | ///////////////////////////////////////////////////////////////////////////////////////////////// 4 | // 5 | // FSharp.Control.FusionTasks - F# Async workflow <--> .NET Task easy seamless interoperability library. 6 | // Copyright (c) 2016-2021 Kouji Matsui (@kozy_kekyo, @kekyo2) 7 | // 8 | // Licensed under the Apache License, Version 2.0 (the "License"); 9 | // you may not use this file except in compliance with the License. 10 | // You may obtain a copy of the License at 11 | // 12 | // http://www.apache.org/licenses/LICENSE-2.0 13 | // 14 | // Unless required by applicable law or agreed to in writing, software 15 | // distributed under the License is distributed on an "AS IS" BASIS, 16 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | // See the License for the specific language governing permissions and 18 | // limitations under the License. 19 | // 20 | ///////////////////////////////////////////////////////////////////////////////////////////////// 21 | 22 | extensions: .xml .xaml .axaml .config .xsd 23 | .NET Task easy seamless interoperability library. 27 | // Copyright (c) 2016-2021 Kouji Matsui (@kozy_kekyo, @kekyo2) 28 | // 29 | // Licensed under the Apache License, Version 2.0 (the "License"); 30 | // you may not use this file except in compliance with the License. 31 | // You may obtain a copy of the License at 32 | // 33 | // http://www.apache.org/licenses/LICENSE-2.0 34 | // 35 | // Unless required by applicable law or agreed to in writing, software 36 | // distributed under the License is distributed on an "AS IS" BASIS, 37 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 38 | // See the License for the specific language governing permissions and 39 | // limitations under the License. 40 | // 41 | ///////////////////////////////////////////////////////////////////////////////////////////////// 42 | --> -------------------------------------------------------------------------------- /FSharp.Control.FusionTasks/AsyncCompletionSource.fs: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // FSharp.Control.FusionTasks - F# Async workflow <--> .NET Task easy seamless interoperability library. 4 | // Copyright (c) 2016-2021 Kouji Matsui (@kozy_kekyo, @kekyo2) 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | ///////////////////////////////////////////////////////////////////////////////////////////////// 19 | 20 | namespace Microsoft.FSharp.Control 21 | 22 | open System 23 | 24 | /// 25 | /// Delegation F#'s async continuation. 26 | /// 27 | /// 28 | /// Simulate TaskCompletionSource<'T> for F#'s Async<'T>. 29 | /// 30 | /// Computation result type 31 | [] 32 | type AsyncCompletionSource<'T> = 33 | 34 | [] 35 | val mutable private _completed : 'T -> unit 36 | [] 37 | val mutable private _caught : exn -> unit 38 | [] 39 | val mutable private _canceled : OperationCanceledException -> unit 40 | 41 | val private _async : Async<'T> 42 | 43 | /// 44 | /// Constructor. 45 | /// 46 | new () as this = { 47 | _async = Async.FromContinuations<'T>(fun (completed, caught, canceled) -> 48 | this._completed <- completed 49 | this._caught <- caught 50 | this._canceled <- canceled) 51 | } 52 | 53 | /// 54 | /// Target Async<'T> instance. 55 | /// 56 | member this.Async = this._async 57 | 58 | /// 59 | /// Set result value and continue continuation. 60 | /// 61 | /// Result value 62 | member this.SetResult value = this._completed value 63 | 64 | /// 65 | /// Set exception and continue continuation. 66 | /// 67 | /// Exception instance 68 | member this.SetException exn = this._caught exn 69 | 70 | /// 71 | /// Cancel async computation. 72 | /// 73 | member this.SetCanceled() = 74 | this._canceled(Utilities.createCanceledException(None)) 75 | 76 | /// 77 | /// Cancel async computation. 78 | /// 79 | /// CancellationToken 80 | member this.SetCanceled token = 81 | this._canceled(Utilities.createCanceledException(Some token)) 82 | -------------------------------------------------------------------------------- /FSharp.Control.FusionTasks/AsyncExtensions.fs: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // FSharp.Control.FusionTasks - F# Async workflow <--> .NET Task easy seamless interoperability library. 4 | // Copyright (c) 2016-2021 Kouji Matsui (@kozy_kekyo, @kekyo2) 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | ///////////////////////////////////////////////////////////////////////////////////////////////// 19 | 20 | namespace Microsoft.FSharp.Control 21 | 22 | open System 23 | open System.Collections.Generic 24 | open System.Runtime.CompilerServices 25 | open System.Threading 26 | open System.Threading.Tasks 27 | 28 | #nowarn "44" 29 | 30 | [] 31 | [] 32 | module AsyncExtensions = 33 | 34 | /////////////////////////////////////////////////////////////////////////////////// 35 | // F# side Async class extensions. 36 | 37 | type Async with 38 | 39 | /// 40 | /// Seamless conversion from F# Async to .NET Task. 41 | /// 42 | /// F# Async 43 | /// Cancellation token (optional) 44 | /// .NET Task instance 45 | static member AsTask(async: Async, ?token: CancellationToken) = 46 | Infrastructures.asTaskT(async, token) :> Task 47 | 48 | /// 49 | /// Seamless conversion from F# Async to .NET Task. 50 | /// 51 | /// Computation result type 52 | /// F# Async 53 | /// Cancellation token (optional) 54 | /// .NET Task instance 55 | static member AsTask(async: Async<'T>, ?token: CancellationToken) = 56 | Infrastructures.asTaskT(async, token) 57 | 58 | //////////////////////////////////////////////////////////////////// 59 | // Operators 60 | 61 | /// 62 | /// Seamless conversion operator from .NET Task to F# Async. 63 | /// 64 | /// .NET Task instance 65 | /// F# Async 66 | static member AsAsync (task: Task) = 67 | Infrastructures.asAsync(task, None) 68 | 69 | /// 70 | /// Seamless conversion operator from .NET Task to F# Async. 71 | /// 72 | /// Computation result type 73 | /// .NET Task instance 74 | /// F# Async 75 | static member AsAsync (task: Task<'T>) = 76 | Infrastructures.asAsyncT(task, None) 77 | 78 | /// 79 | /// Seamless conversion operator from .NET ValueTask to F# Async. 80 | /// 81 | /// .NET ValueTask instance 82 | /// F# Async 83 | static member AsAsync (task: ValueTask) = 84 | Infrastructures.asAsyncV(task, None) 85 | 86 | /// 87 | /// Seamless conversion operator from .NET ValueTask to F# Async. 88 | /// 89 | /// Computation result type 90 | /// .NET ValueTask instance 91 | /// F# Async 92 | static member AsAsync (task: ValueTask<'T>) = 93 | Infrastructures.asAsyncVT(task, None) 94 | 95 | /// 96 | /// Seamless conversion operator from .NET Task to F# Async. 97 | /// 98 | /// .NET Configured Task instance 99 | /// F# Async 100 | static member AsAsync (ct: ConfiguredTaskAwaitable) = 101 | Infrastructures.asAsyncCT(ct) 102 | 103 | /// 104 | /// Seamless conversion operator from .NET Task to F# Async. 105 | /// 106 | /// Computation result type 107 | /// .NET Configured Task instance 108 | /// F# Async 109 | static member AsAsync (ctt: ConfiguredTaskAwaitable<'T>) = 110 | Infrastructures.asAsyncCTT(ctt) 111 | 112 | /// 113 | /// Seamless conversion operator from .NET ValueTask to F# Async. 114 | /// 115 | /// .NET Configured ValueTask instance 116 | /// F# Async 117 | static member AsAsync (cvt: ConfiguredValueTaskAwaitable) = 118 | Infrastructures.asAsyncCVT(cvt) 119 | 120 | /// 121 | /// Seamless conversion operator from .NET ValueTask to F# Async. 122 | /// 123 | /// Computation result type 124 | /// .NET Configured ValueTask instance 125 | /// F# Async 126 | static member AsAsync (cvtt: ConfiguredValueTaskAwaitable<'T>) = 127 | Infrastructures.asAsyncCVTT(cvtt) 128 | 129 | /////////////////////////////////////////////////////////////////////////////////// 130 | // F# side Task class extensions. 131 | 132 | type Task with 133 | 134 | /// 135 | /// Seamless conversion from .NET Task to F# Async. 136 | /// 137 | /// Cancellation token (optional) 138 | /// F# Async instance 139 | member task.AsAsync(?token: CancellationToken) = 140 | Infrastructures.asAsync(task, token) 141 | 142 | /// 143 | /// Seamless conversionable substitution Task.ConfigureAwait() 144 | /// 145 | /// True if continuation running on captured SynchronizationContext 146 | /// ConfiguredAsyncAwaitable instance 147 | [] 148 | member task.AsyncConfigure(continueOnCapturedContext: bool) = 149 | ConfiguredTaskAsyncAwaitable(task.ConfigureAwait(continueOnCapturedContext)) 150 | 151 | type Task<'T> with 152 | 153 | /// 154 | /// Seamless conversion from .NET Task to F# Async. 155 | /// 156 | /// Computation result type 157 | /// Cancellation token (optional) 158 | /// F# Async instance 159 | member task.AsAsync(?token: CancellationToken) = 160 | Infrastructures.asAsyncT(task, token) 161 | 162 | /// 163 | /// Seamless conversionable substitution Task.ConfigureAwait() 164 | /// 165 | /// Computation result type 166 | /// True if continuation running on captured SynchronizationContext 167 | /// ConfiguredAsyncAwaitable instance 168 | [] 169 | member task.AsyncConfigure(continueOnCapturedContext: bool) = 170 | ConfiguredTaskAsyncAwaitable<'T>(task.ConfigureAwait(continueOnCapturedContext)) 171 | 172 | type ValueTask with 173 | 174 | /// 175 | /// Seamless conversion from .NET ValueTask to F# Async. 176 | /// 177 | /// Cancellation token (optional) 178 | /// F# Async instance 179 | member task.AsAsync(?token: CancellationToken) = 180 | Infrastructures.asAsyncV(task, token) 181 | 182 | /// 183 | /// Seamless conversionable substitution ValueTask.ConfigureAwait() 184 | /// 185 | /// True if continuation running on captured SynchronizationContext 186 | /// ConfiguredAsyncAwaitable instance 187 | [] 188 | member task.AsyncConfigure(continueOnCapturedContext: bool) = 189 | ConfiguredValueTaskAsyncAwaitable(task.ConfigureAwait(continueOnCapturedContext)) 190 | 191 | type ValueTask<'T> with 192 | 193 | /// 194 | /// Seamless conversion from .NET ValueTask to F# Async. 195 | /// 196 | /// Computation result type 197 | /// Cancellation token (optional) 198 | /// F# Async instance 199 | member task.AsAsync(?token: CancellationToken) = 200 | Infrastructures.asAsyncVT(task, token) 201 | 202 | /// 203 | /// Seamless conversionable substitution ValueTask.ConfigureAwait() 204 | /// 205 | /// Computation result type 206 | /// True if continuation running on captured SynchronizationContext 207 | /// ConfiguredAsyncAwaitable instance 208 | [] 209 | member task.AsyncConfigure(continueOnCapturedContext: bool) = 210 | ConfiguredValueTaskAsyncAwaitable<'T>(task.ConfigureAwait(continueOnCapturedContext)) 211 | 212 | /////////////////////////////////////////////////////////////////////////////////// 213 | // F# side ConfiguredAsyncAwaitable class extensions. 214 | 215 | type ConfiguredTaskAsyncAwaitable with 216 | 217 | /// 218 | /// Seamless conversion from .NET Task to F# Async. 219 | /// 220 | /// F# Async instance. 221 | [] 222 | member cta.AsAsync() = 223 | Infrastructures.asAsyncCTA(cta) 224 | 225 | type ConfiguredTaskAsyncAwaitable<'T> with 226 | 227 | /// 228 | /// Seamless conversion from .NET Task to F# Async. 229 | /// 230 | /// Computation result type 231 | /// F# Async instance. 232 | [] 233 | member cta.AsAsync() = 234 | Infrastructures.asAsyncCTAT(cta) 235 | 236 | type ConfiguredValueTaskAsyncAwaitable with 237 | 238 | /// 239 | /// Seamless conversion from .NET Task to F# Async. 240 | /// 241 | /// F# Async instance. 242 | [] 243 | member cta.AsAsync() = 244 | Infrastructures.asAsyncCVTA(cta) 245 | 246 | type ConfiguredValueTaskAsyncAwaitable<'T> with 247 | 248 | /// 249 | /// Seamless conversion from .NET Task to F# Async. 250 | /// 251 | /// Computation result type 252 | /// F# Async instance. 253 | [] 254 | member cta.AsAsync() = 255 | Infrastructures.asAsyncCVTAT(cta) 256 | 257 | /////////////////////////////////////////////////////////////////////////////////// 258 | // F# side async computation builder extensions. 259 | 260 | type AsyncBuilder with 261 | 262 | /// 263 | /// Bypass default Async binder. 264 | /// 265 | /// F# Async instance. 266 | /// F# Async instance. 267 | member __.Source(computation: Async<'T>) = 268 | computation 269 | 270 | /// 271 | /// Seamless conversion from .NET Task to F# Async in Async workflow. 272 | /// 273 | /// .NET Task (expression result) 274 | /// F# Async instance. 275 | member __.Source(task: Task) = 276 | Infrastructures.asAsync(task, None) 277 | 278 | /// 279 | /// Seamless conversion from .NET Task to F# Async in Async workflow. 280 | /// 281 | /// Computation result type 282 | /// .NET Task<'T> (expression result) 283 | /// F# Async instance. 284 | member __.Source(task: Task<'T>) = 285 | Infrastructures.asAsyncT(task, None) 286 | 287 | /// 288 | /// Seamless conversion from .NET Task to F# Async in Async workflow. 289 | /// 290 | /// .NET ConfiguredTaskAwaitable (expr.ConfigureAwait(...)) 291 | /// F# Async instance. 292 | member __.Source(ct: ConfiguredTaskAwaitable) = 293 | Infrastructures.asAsyncCT(ct) 294 | 295 | /// 296 | /// Seamless conversion from .NET Task to F# Async in Async workflow. 297 | /// 298 | /// Computation result type 299 | /// .NET ConfiguredTaskAwaitable<'T> (expr.ConfigureAwait(...)) 300 | /// F# Async instance. 301 | member __.Source(ctt: ConfiguredTaskAwaitable<'T>) = 302 | Infrastructures.asAsyncCTT(ctt) 303 | 304 | /// 305 | /// Seamless conversion from .NET Task to F# Async in Async workflow. 306 | /// 307 | /// .NET ValueTask (expression result) 308 | /// F# Async instance. 309 | member __.Source(task: ValueTask) = 310 | Infrastructures.asAsyncV(task, None) 311 | 312 | /// 313 | /// Seamless conversion from .NET Task to F# Async in Async workflow. 314 | /// 315 | /// Computation result type 316 | /// .NET ValueTask<'T> (expression result) 317 | /// F# Async instance. 318 | member __.Source(task: ValueTask<'T>) = 319 | Infrastructures.asAsyncVT(task, None) 320 | 321 | /// 322 | /// Seamless conversion from .NET Task to F# Async in Async workflow. 323 | /// 324 | /// .NET ConfiguredValueTaskAwaitable (expr.ConfigureAwait(...)) 325 | /// F# Async instance. 326 | member __.Source(cvt: ConfiguredValueTaskAwaitable) = 327 | Infrastructures.asAsyncCVT(cvt) 328 | 329 | /// 330 | /// Seamless conversion from .NET Task to F# Async in Async workflow. 331 | /// 332 | /// Computation result type 333 | /// .NET ConfiguredValueTaskAwaitable<'T> (expr.ConfigureAwait(...)) 334 | /// F# Async instance. 335 | member __.Source(cvtt: ConfiguredValueTaskAwaitable<'T>) = 336 | Infrastructures.asAsyncCVTT(cvtt) 337 | 338 | /// 339 | /// Accept any sequence type to support `for .. in` expressions in Async workflows. 340 | /// 341 | /// The element type of the sequence 342 | /// The sequence. 343 | /// The sequence. 344 | member __.Source(s: 'E seq) = 345 | s 346 | 347 | /// 348 | /// Seamless conversion from .NET Task to F# Async in Async workflow. 349 | /// 350 | /// .NET ConfiguredTaskAsyncAwaitable (expr.AsyncConfigure(...)) 351 | /// F# Async instance. 352 | [] 353 | member __.Source(cta: ConfiguredTaskAsyncAwaitable) = 354 | Infrastructures.asAsyncCTA(cta) 355 | 356 | /// 357 | /// Seamless conversion from .NET Task to F# Async in Async workflow. 358 | /// 359 | /// Computation result type 360 | /// .NET ConfiguredTaskAsyncAwaitable<'T> (expr.AsyncConfigure(...)) 361 | /// F# Async instance. 362 | [] 363 | member __.Source(cta: ConfiguredTaskAsyncAwaitable<'T>) = 364 | Infrastructures.asAsyncCTAT(cta) 365 | 366 | /// 367 | /// Seamless conversion from .NET Task to F# Async in Async workflow. 368 | /// 369 | /// .NET ConfiguredValueTaskAsyncAwaitable (expr.AsyncConfigure(...)) 370 | /// F# Async instance. 371 | [] 372 | member __.Source(cta: ConfiguredValueTaskAsyncAwaitable) = 373 | Infrastructures.asAsyncCVTA(cta) 374 | 375 | /// 376 | /// Seamless conversion from .NET Task to F# Async in Async workflow. 377 | /// 378 | /// Computation result type 379 | /// .NET ConfiguredValueTaskAsyncAwaitable<'T> (expr.AsyncConfigure(...)) 380 | /// F# Async instance. 381 | [] 382 | member __.Source(cta: ConfiguredValueTaskAsyncAwaitable<'T>) = 383 | Infrastructures.asAsyncCVTAT(cta) 384 | 385 | #if !NET45 && !NETSTANDARD1_6 && !NETCOREAPP2_0 386 | 387 | /// 388 | /// Seamless conversion from .NET Async disposer (IAsyncDisposable) to F# Async in Async workflow. 389 | /// 390 | /// .NET IAsyncDisposable 391 | /// use expression body 392 | /// F# Async instance. 393 | member __.Using(disposable: 'T :> IAsyncDisposable, body: 'T -> Async<'R>) : Async<'R> = 394 | Infrastructures.asAsyncAD(disposable, body) 395 | 396 | /// 397 | /// Seamless conversion from .NET Async sequence (IAsyncEnumerable<'E>) to F# Async in Async workflow. 398 | /// 399 | /// The element type of the sequence 400 | /// .NET IAsyncEnumerable<'E> (expression result) 401 | /// for expression body 402 | /// F# Async instance. 403 | member __.For(enumerable: IAsyncEnumerable<'E>, body: 'E -> Async<'R>) = 404 | Infrastructures.asAsyncAE(enumerable, body, None) 405 | 406 | /// 407 | /// Seamless conversion from .NET Async sequence (IAsyncEnumerable<'E>) to F# Async in Async workflow. 408 | /// 409 | /// The element type of the sequence 410 | /// .NET IAsyncEnumerable<'E> (expression result) 411 | /// for expression body 412 | /// F# Async instance. 413 | member __.For(enumerable: ConfiguredCancelableAsyncEnumerable<'E>, body: 'E -> Async<'R>) = 414 | Infrastructures.asAsyncCCAE(enumerable, body) 415 | 416 | /// 417 | /// Accept any async sequence type to support `for .. in` expressions in Async workflows. 418 | /// 419 | /// The element type of the sequence 420 | /// .NET IAsyncEnumerable<'E> (expression result) 421 | /// The sequence. 422 | member __.Source(enumerable: IAsyncEnumerable<'E>) = 423 | enumerable 424 | 425 | /// 426 | /// Accept any async sequence type to support `for .. in` expressions in Async workflows. 427 | /// 428 | /// The element type of the sequence 429 | /// .NET ConfiguredCancelableAsyncEnumerable<'E> (expression result) 430 | /// The sequence. 431 | member __.Source(enumerable: ConfiguredCancelableAsyncEnumerable<'E>) = 432 | enumerable 433 | #endif 434 | -------------------------------------------------------------------------------- /FSharp.Control.FusionTasks/Awaiters.fs: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // FSharp.Control.FusionTasks - F# Async workflow <--> .NET Task easy seamless interoperability library. 4 | // Copyright (c) 2016-2021 Kouji Matsui (@kozy_kekyo, @kekyo2) 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | ///////////////////////////////////////////////////////////////////////////////////////////////// 19 | 20 | namespace Microsoft.FSharp.Control 21 | 22 | open System 23 | open System.Threading 24 | open System.Threading.Tasks 25 | open System.Runtime.CompilerServices 26 | 27 | #nowarn "44" 28 | 29 | // Provide Awaitable/Awaiter on custom code: 30 | // PCL's Awaitable/Awaiter on "Microsoft.Runtime.CompilerServices", 31 | // but not decorated TypeForwardedTo attributes. 32 | // So if referenced Awaitable/Awaiter types, cause cannot found Awaitable/Awaiter types. 33 | // (Awaitable/Awaiter types defined on "System.Runtime.CompilerServices" real assembly) 34 | // FusionTasks redefined custom Awaitable/Awaiter types, exclude CompilerServices's type references. 35 | 36 | /////////////////////////////////////////////////////////////////////////////////// 37 | // AsyncAwaiter. 38 | 39 | /// 40 | /// F# Async's awaiter implementation. This structure using implicitly. 41 | /// 42 | [] 43 | type AsyncAwaiter internal (ta: TaskAwaiter) = 44 | 45 | member __.IsCompleted = ta.IsCompleted 46 | member __.OnCompleted(continuation: Action) = ta.OnCompleted(continuation) 47 | member __.UnsafeOnCompleted(continuation: Action) = ta.UnsafeOnCompleted(continuation) 48 | member __.GetResult() = ta.GetResult() 49 | 50 | interface System.Runtime.CompilerServices.INotifyCompletion with 51 | member __.OnCompleted(continuation: Action) = ta.OnCompleted(continuation) 52 | 53 | /// 54 | /// F# Async's awaiter implementation. This structure using implicitly. 55 | /// 56 | [] 57 | type AsyncAwaiter<'T> internal (ta: TaskAwaiter<'T>) = 58 | 59 | member __.IsCompleted = ta.IsCompleted 60 | member __.OnCompleted(continuation: Action) = ta.OnCompleted(continuation) 61 | member __.UnsafeOnCompleted(continuation: Action) = ta.UnsafeOnCompleted(continuation) 62 | member __.GetResult() = ta.GetResult() 63 | 64 | interface System.Runtime.CompilerServices.INotifyCompletion with 65 | member __.OnCompleted(continuation: Action) = ta.OnCompleted(continuation) 66 | 67 | /////////////////////////////////////////////////////////////////////////////////// 68 | // ConfiguredTaskAsyncAwaiter. 69 | 70 | /// 71 | /// F# Async's awaiter implementation. This structure using implicitly. 72 | /// 73 | [] 74 | [] 75 | type ConfiguredTaskAsyncAwaiter internal (ctacta: ConfiguredTaskAwaitable.ConfiguredTaskAwaiter) = 76 | 77 | member __.IsCompleted = ctacta.IsCompleted 78 | member __.OnCompleted(continuation: Action) = ctacta.OnCompleted(continuation) 79 | member __.UnsafeOnCompleted(continuation: Action) = ctacta.UnsafeOnCompleted(continuation) 80 | member __.GetResult() = ctacta.GetResult() 81 | 82 | interface System.Runtime.CompilerServices.INotifyCompletion with 83 | member __.OnCompleted(continuation: Action) = ctacta.OnCompleted(continuation) 84 | 85 | /// 86 | /// F# Async's awaitable implementation. This structure using implicitly. 87 | /// 88 | [] 89 | [] 90 | type ConfiguredTaskAsyncAwaitable internal (cta: ConfiguredTaskAwaitable) = 91 | 92 | member __.GetAwaiter() = ConfiguredTaskAsyncAwaiter(cta.GetAwaiter()) 93 | 94 | /// 95 | /// F# Async's awaiter implementation. This structure using implicitly. 96 | /// 97 | [] 98 | [] 99 | type ConfiguredTaskAsyncAwaiter<'T> internal (ctacta: ConfiguredTaskAwaitable<'T>.ConfiguredTaskAwaiter) = 100 | 101 | member __.IsCompleted = ctacta.IsCompleted 102 | member __.OnCompleted(continuation: Action) = ctacta.OnCompleted(continuation) 103 | member __.UnsafeOnCompleted(continuation: Action) = ctacta.UnsafeOnCompleted(continuation) 104 | member __.GetResult() = ctacta.GetResult() 105 | 106 | interface System.Runtime.CompilerServices.INotifyCompletion with 107 | member __.OnCompleted(continuation: Action) = ctacta.OnCompleted(continuation) 108 | 109 | /// 110 | /// F# Async's awaitable implementation. This structure using implicitly. 111 | /// 112 | [] 113 | [] 114 | type ConfiguredTaskAsyncAwaitable<'T> internal (cta: ConfiguredTaskAwaitable<'T>) = 115 | 116 | member __.GetAwaiter() = ConfiguredTaskAsyncAwaiter<'T>(cta.GetAwaiter()) 117 | 118 | /////////////////////////////////////////////////////////////////////////////////// 119 | // ConfiguredValueTaskAsyncAwaiter. 120 | 121 | /// 122 | /// F# Async's awaiter implementation. This structure using implicitly. 123 | /// 124 | [] 125 | [] 126 | type ConfiguredValueTaskAsyncAwaiter internal (ctacta: ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter) = 127 | 128 | member __.IsCompleted = ctacta.IsCompleted 129 | member __.OnCompleted(continuation: Action) = ctacta.OnCompleted(continuation) 130 | member __.UnsafeOnCompleted(continuation: Action) = ctacta.UnsafeOnCompleted(continuation) 131 | member __.GetResult() = ctacta.GetResult() 132 | 133 | interface System.Runtime.CompilerServices.INotifyCompletion with 134 | member __.OnCompleted(continuation: Action) = ctacta.OnCompleted(continuation) 135 | 136 | /// 137 | /// F# Async's awaitable implementation. This structure using implicitly. 138 | /// 139 | [] 140 | [] 141 | type ConfiguredValueTaskAsyncAwaitable internal (cta: ConfiguredValueTaskAwaitable) = 142 | 143 | member __.GetAwaiter() = ConfiguredValueTaskAsyncAwaiter(cta.GetAwaiter()) 144 | 145 | /// 146 | /// F# Async's awaiter implementation. This structure using implicitly. 147 | /// 148 | [] 149 | [] 150 | type ConfiguredValueTaskAsyncAwaiter<'T> internal (ctacta: ConfiguredValueTaskAwaitable<'T>.ConfiguredValueTaskAwaiter) = 151 | 152 | member __.IsCompleted = ctacta.IsCompleted 153 | member __.OnCompleted(continuation: Action) = ctacta.OnCompleted(continuation) 154 | member __.UnsafeOnCompleted(continuation: Action) = ctacta.UnsafeOnCompleted(continuation) 155 | member __.GetResult() = ctacta.GetResult() 156 | 157 | interface System.Runtime.CompilerServices.INotifyCompletion with 158 | member __.OnCompleted(continuation: Action) = ctacta.OnCompleted(continuation) 159 | 160 | /// 161 | /// F# Async's awaitable implementation. This structure using implicitly. 162 | /// 163 | [] 164 | [] 165 | type ConfiguredValueTaskAsyncAwaitable<'T> internal (cta: ConfiguredValueTaskAwaitable<'T>) = 166 | 167 | member __.GetAwaiter() = ConfiguredValueTaskAsyncAwaiter<'T>(cta.GetAwaiter()) 168 | -------------------------------------------------------------------------------- /FSharp.Control.FusionTasks/CommonAssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // FSharp.Control.FusionTasks - F# Async workflow <--> .NET Task easy seamless interoperability library. 4 | // Copyright (c) 2016-2021 Kouji Matsui (@kozy_kekyo, @kekyo2) 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | ///////////////////////////////////////////////////////////////////////////////////////////////// 19 | 20 | namespace global 21 | [] 22 | do() 23 | -------------------------------------------------------------------------------- /FSharp.Control.FusionTasks/FSharp.Control.FusionTasks.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net45;net461;net48;netstandard1.6;netstandard2.0;netstandard2.1;netcoreapp2.0;netcoreapp2.1;netcoreapp2.2;netcoreapp3.0;netcoreapp3.1;net5.0;net6.0 5 | bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /FSharp.Control.FusionTasks/Infrastructures.fs: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // FSharp.Control.FusionTasks - F# Async workflow <--> .NET Task easy seamless interoperability library. 4 | // Copyright (c) 2016-2021 Kouji Matsui (@kozy_kekyo, @kekyo2) 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | ///////////////////////////////////////////////////////////////////////////////////////////////// 19 | 20 | namespace Microsoft.FSharp.Control 21 | 22 | open System 23 | open System.Threading 24 | open System.Threading.Tasks 25 | open System.Collections.Generic 26 | open System.Runtime.CompilerServices 27 | 28 | #nowarn "44" 29 | 30 | /////////////////////////////////////////////////////////////////////////////////// 31 | // Internal implementations. 32 | 33 | module internal Infrastructures = 34 | 35 | let inline private (|IsFaulted|IsCanceled|IsCompleted|) (task: Task) = 36 | if task.IsFaulted then IsFaulted task.Exception 37 | else if task.IsCanceled then IsCanceled 38 | else IsCompleted 39 | 40 | let inline private safeToken (ct: CancellationToken option) = 41 | match ct with 42 | | Some token -> token 43 | | None -> Async.DefaultCancellationToken 44 | 45 | let asTaskT(async: Async<'T>, ct: CancellationToken option) = 46 | Async.StartImmediateAsTask(async, safeToken ct) 47 | 48 | let asValueTask(async: Async, ct: CancellationToken option) = 49 | ValueTask(Async.StartImmediateAsTask(async, safeToken ct)) 50 | 51 | let asValueTaskT(async: Async<'T>, ct: CancellationToken option) = 52 | ValueTask<'T>(Async.StartImmediateAsTask(async, safeToken ct)) 53 | 54 | let asAsync(task: Task, ct: CancellationToken option) = 55 | let scheduler = Utilities.getScheduler() 56 | Async.FromContinuations( 57 | fun (completed, caught, canceled) -> 58 | task.ContinueWith( 59 | new Action(fun _ -> 60 | match task with 61 | | IsFaulted exn -> caught(exn) 62 | | IsCanceled -> canceled(Utilities.createCanceledException ct) // TODO: how to extract implicit caught exceptions from task? 63 | | IsCompleted -> completed(())), 64 | safeToken ct, 65 | TaskContinuationOptions.AttachedToParent, 66 | scheduler) 67 | |> ignore) 68 | 69 | let asAsyncT(task: Task<'T>, ct: CancellationToken option) = 70 | let scheduler = Utilities.getScheduler() 71 | Async.FromContinuations( 72 | fun (completed, caught, canceled) -> 73 | task.ContinueWith( 74 | new Action>(fun _ -> 75 | match task with 76 | | IsFaulted exn -> caught(exn) 77 | | IsCanceled -> canceled(Utilities.createCanceledException ct) // TODO: how to extract implicit caught exceptions from task? 78 | | IsCompleted -> completed(task.Result)), 79 | safeToken ct, 80 | TaskContinuationOptions.AttachedToParent, 81 | scheduler) 82 | |> ignore) 83 | 84 | let asAsyncV(task: ValueTask, ct: CancellationToken option) = 85 | let scheduler = Utilities.getScheduler() 86 | Async.FromContinuations( 87 | fun (completed, caught, canceled) -> 88 | match task.IsCompletedSuccessfully with 89 | | true -> completed() 90 | | false -> 91 | let task = task.AsTask() 92 | task.ContinueWith( 93 | new Action(fun _ -> 94 | match task with 95 | | IsFaulted exn -> caught(exn) 96 | | IsCanceled -> canceled(Utilities.createCanceledException ct) // TODO: how to extract implicit caught exceptions from task? 97 | | IsCompleted -> completed()), 98 | safeToken ct, 99 | TaskContinuationOptions.AttachedToParent, 100 | scheduler) 101 | |> ignore) 102 | 103 | let asAsyncVT(task: ValueTask<'T>, ct: CancellationToken option) = 104 | let scheduler = Utilities.getScheduler() 105 | Async.FromContinuations( 106 | fun (completed, caught, canceled) -> 107 | match task.IsCompletedSuccessfully with 108 | | true -> completed(task.Result) 109 | | false -> 110 | let task = task.AsTask() 111 | task.ContinueWith( 112 | new Action>(fun _ -> 113 | match task with 114 | | IsFaulted exn -> caught(exn) 115 | | IsCanceled -> canceled(Utilities.createCanceledException ct) // TODO: how to extract implicit caught exceptions from task? 116 | | IsCompleted -> completed(task.Result)), 117 | safeToken ct, 118 | TaskContinuationOptions.AttachedToParent, 119 | scheduler) 120 | |> ignore) 121 | 122 | let asAsyncCT(ct: ConfiguredTaskAwaitable) = 123 | Async.FromContinuations( 124 | fun (completed, caught, _) -> 125 | let awaiter = ct.GetAwaiter() 126 | awaiter.OnCompleted( 127 | new Action(fun _ -> 128 | try 129 | awaiter.GetResult() 130 | completed() 131 | with exn -> caught(exn))) 132 | |> ignore) 133 | 134 | let asAsyncCTT(cta: ConfiguredTaskAwaitable<'T>) = 135 | Async.FromContinuations( 136 | fun (completed, caught, _) -> 137 | let awaiter = cta.GetAwaiter() 138 | awaiter.OnCompleted( 139 | new Action(fun _ -> 140 | try completed(awaiter.GetResult()) 141 | with exn -> caught(exn))) 142 | |> ignore) 143 | 144 | let asAsyncCVT(cvt: ConfiguredValueTaskAwaitable) = 145 | Async.FromContinuations( 146 | fun (completed, caught, _) -> 147 | let awaiter = cvt.GetAwaiter() 148 | awaiter.OnCompleted( 149 | new Action(fun _ -> 150 | try completed(awaiter.GetResult()) 151 | with exn -> caught(exn))) 152 | |> ignore) 153 | 154 | let asAsyncCVTT(cvtt: ConfiguredValueTaskAwaitable<'T>) = 155 | Async.FromContinuations( 156 | fun (completed, caught, _) -> 157 | let awaiter = cvtt.GetAwaiter() 158 | awaiter.OnCompleted( 159 | new Action(fun _ -> 160 | try completed(awaiter.GetResult()) 161 | with exn -> caught(exn))) 162 | |> ignore) 163 | 164 | [] 165 | let asAsyncCTA(cta: ConfiguredTaskAsyncAwaitable) = 166 | Async.FromContinuations( 167 | fun (completed, caught, _) -> 168 | let awaiter = cta.GetAwaiter() 169 | awaiter.OnCompleted( 170 | new Action(fun _ -> 171 | try 172 | awaiter.GetResult() 173 | completed() 174 | with exn -> caught(exn))) 175 | |> ignore) 176 | 177 | [] 178 | let asAsyncCTAT(cta: ConfiguredTaskAsyncAwaitable<'T>) = 179 | Async.FromContinuations( 180 | fun (completed, caught, _) -> 181 | let awaiter = cta.GetAwaiter() 182 | awaiter.OnCompleted( 183 | new Action(fun _ -> 184 | try completed(awaiter.GetResult()) 185 | with exn -> caught(exn))) 186 | |> ignore) 187 | 188 | [] 189 | let asAsyncCVTA(cta: ConfiguredValueTaskAsyncAwaitable) = 190 | Async.FromContinuations( 191 | fun (completed, caught, _) -> 192 | let awaiter = cta.GetAwaiter() 193 | awaiter.OnCompleted( 194 | new Action(fun _ -> 195 | try completed(awaiter.GetResult()) 196 | with exn -> caught(exn))) 197 | |> ignore) 198 | 199 | [] 200 | let asAsyncCVTAT(cta: ConfiguredValueTaskAsyncAwaitable<'T>) = 201 | Async.FromContinuations( 202 | fun (completed, caught, _) -> 203 | let awaiter = cta.GetAwaiter() 204 | awaiter.OnCompleted( 205 | new Action(fun _ -> 206 | try completed(awaiter.GetResult()) 207 | with exn -> caught(exn))) 208 | |> ignore) 209 | 210 | #if !NET45 && !NETSTANDARD1_6 && !NETCOREAPP2_0 211 | 212 | let private finallyAD (disposable: IAsyncDisposable) (continuation: unit -> unit) (caught: exn -> unit) = 213 | try 214 | let disposeAwaiter = disposable.DisposeAsync().GetAwaiter() 215 | if disposeAwaiter.IsCompleted then 216 | disposeAwaiter.GetResult() 217 | continuation() 218 | else 219 | disposeAwaiter.OnCompleted( 220 | fun () -> 221 | try 222 | disposeAwaiter.GetResult() 223 | continuation() 224 | with 225 | | exn -> caught exn) 226 | with 227 | | exn -> caught exn 228 | 229 | let private finallyCCAEE (disposable: ConfiguredCancelableAsyncEnumerable<'T>.Enumerator) (continuation: unit -> unit) (caught: exn -> unit) = 230 | try 231 | let disposeAwaiter = disposable.DisposeAsync().GetAwaiter() 232 | if disposeAwaiter.IsCompleted then 233 | disposeAwaiter.GetResult() 234 | continuation() 235 | else 236 | disposeAwaiter.OnCompleted( 237 | fun () -> 238 | try 239 | disposeAwaiter.GetResult() 240 | continuation() 241 | with 242 | | exn -> caught exn) 243 | with 244 | | exn -> caught exn 245 | 246 | let asAsyncAD(disposable: 'T :> IAsyncDisposable, body: 'T -> Async<'R>) = 247 | 248 | let bodyAsync = body disposable 249 | 250 | // Wrap asynchronous monad. 251 | Async.FromContinuations( 252 | fun (completed, caught, canceled) -> 253 | 254 | // Finally handlers. 255 | let completedContinuation value = 256 | finallyAD disposable (fun () -> completed value) caught 257 | let caughtContinuation exn = 258 | finallyAD disposable (fun () -> caught exn) caught 259 | let canceledContinuation exn = 260 | finallyAD disposable (fun () -> canceled exn) caught 261 | 262 | Async.StartWithContinuations( 263 | bodyAsync, 264 | // Got: 265 | completedContinuation, 266 | caughtContinuation, 267 | canceledContinuation)) 268 | 269 | let asAsyncAE(enumerable: IAsyncEnumerable<'T>, body: 'T -> Async<'U>, ct: CancellationToken option) = 270 | 271 | let inline checkCancellation() = 272 | if ct.IsSome then 273 | ct.Value.ThrowIfCancellationRequested() 274 | 275 | // Check early cancellation. 276 | checkCancellation() 277 | 278 | // Get asynchronous enumerator. 279 | let enumerator = enumerable.GetAsyncEnumerator(Utilities.unwrap ct) 280 | 281 | // Wrap asynchronous monad. 282 | Async.FromContinuations( 283 | fun (completed, caught, canceled) -> 284 | let mutable finalValue = Unchecked.defaultof<'U> 285 | 286 | let rec whileLoop() = 287 | 288 | // Finally handlers. 289 | let completedContinuation value = 290 | finallyAD enumerator (fun () -> completed value) caught 291 | let caughtContinuation exn = 292 | finallyAD enumerator (fun () -> caught exn) caught 293 | let canceledContinuation exn = 294 | finallyAD enumerator (fun () -> canceled exn) caught 295 | 296 | // (Recursive) Loop main: 297 | try 298 | // Check early cancellation. 299 | checkCancellation() 300 | 301 | let moveNextAwaiter = enumerator.MoveNextAsync().GetAwaiter() 302 | 303 | // Will get result and invoke continuation. 304 | let getResultContinuation() = 305 | // Got next (asynchronous) value? 306 | let moveNextResult = moveNextAwaiter.GetResult() 307 | if moveNextResult then 308 | // Got Async<'U> 309 | let resultAsync = body enumerator.Current 310 | // Will get value asynchronously. 311 | Async.StartWithContinuations( 312 | resultAsync, 313 | // Got: 314 | (fun result -> 315 | // Save last value 316 | finalValue <- result 317 | // NOTE: Maybe will not cause stack overflow, because async workflow will be scattered recursive calls... 318 | whileLoop()), 319 | // Caught asynchronous monadic exception. 320 | caughtContinuation, 321 | // Caught asynchronous monadic cancel exception. 322 | canceledContinuation) 323 | // Didn't get next value (= finished) 324 | else 325 | // Completed totally asynchronous sequence. 326 | completedContinuation finalValue 327 | 328 | // Already completed synchronously MoveNextAsync() ? 329 | if moveNextAwaiter.IsCompleted then 330 | // Get result synchronously. 331 | getResultContinuation() 332 | else 333 | // Delay getting result. 334 | moveNextAwaiter.OnCompleted( 335 | fun () -> 336 | try 337 | getResultContinuation() 338 | with 339 | | exn -> caughtContinuation exn) 340 | with 341 | | exn -> caughtContinuation exn 342 | 343 | // Start simulated asynchronous loop. 344 | whileLoop()) 345 | 346 | let asAsyncCCAE(enumerable: ConfiguredCancelableAsyncEnumerable<'T>, body: 'T -> Async<'U>) = 347 | 348 | // Get asynchronous enumerator. 349 | let enumerator = enumerable.GetAsyncEnumerator() 350 | 351 | // Wrap asynchronous monad. 352 | Async.FromContinuations( 353 | fun (completed, caught, canceled) -> 354 | let mutable finalValue = Unchecked.defaultof<'U> 355 | 356 | let rec whileLoop() = 357 | 358 | // Finally handlers. 359 | let completedContinuation value = 360 | finallyCCAEE enumerator (fun () -> completed value) caught 361 | let caughtContinuation exn = 362 | finallyCCAEE enumerator (fun () -> caught exn) caught 363 | let canceledContinuation exn = 364 | finallyCCAEE enumerator (fun () -> canceled exn) caught 365 | 366 | // (Recursive) Loop main: 367 | try 368 | let moveNextAwaiter = enumerator.MoveNextAsync().GetAwaiter() 369 | 370 | // Will get result and invoke continuation. 371 | let getResultContinuation() = 372 | // Got next (asynchronous) value? 373 | let moveNextResult = moveNextAwaiter.GetResult() 374 | if moveNextResult then 375 | // Got Async<'U> 376 | let resultAsync = body enumerator.Current 377 | // Will get value asynchronously. 378 | Async.StartWithContinuations( 379 | resultAsync, 380 | // Got: 381 | (fun result -> 382 | // Save last value 383 | finalValue <- result 384 | // NOTE: Maybe will not cause stack overflow, because async workflow will be scattered recursive calls... 385 | whileLoop()), 386 | // Caught asynchronous monadic exception. 387 | caughtContinuation, 388 | // Caught asynchronous monadic cancel exception. 389 | canceledContinuation) 390 | // Didn't get next value (= finished) 391 | else 392 | // Completed totally asynchronous sequence. 393 | completedContinuation finalValue 394 | 395 | // Already completed synchronously MoveNextAsync() ? 396 | if moveNextAwaiter.IsCompleted then 397 | // Get result synchronously. 398 | getResultContinuation() 399 | else 400 | // Delay getting result. 401 | moveNextAwaiter.OnCompleted( 402 | fun () -> 403 | try 404 | getResultContinuation() 405 | with 406 | | exn -> caughtContinuation exn) 407 | with 408 | | exn -> caughtContinuation exn 409 | 410 | // Start simulated asynchronous loop. 411 | whileLoop()) 412 | 413 | #endif 414 | -------------------------------------------------------------------------------- /FSharp.Control.FusionTasks/TaskExtensions.fs: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // FSharp.Control.FusionTasks - F# Async workflow <--> .NET Task easy seamless interoperability library. 4 | // Copyright (c) 2016-2021 Kouji Matsui (@kozy_kekyo, @kekyo2) 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | ///////////////////////////////////////////////////////////////////////////////////////////////// 19 | 20 | namespace System.Threading.Tasks 21 | 22 | open System.Runtime.CompilerServices 23 | open System.Threading 24 | open Microsoft.FSharp.Control 25 | 26 | #nowarn "44" 27 | 28 | /// 29 | /// Seamless conversion extensions in standard .NET Task based infrastructure. 30 | /// 31 | [] 32 | type TaskExtensions = 33 | 34 | /////////////////////////////////////////////////////////////////////////////////// 35 | // .NET (C#) side Task --> Async conversion extensions. 36 | 37 | /// 38 | /// Seamless conversion from .NET Task to F# Async. 39 | /// 40 | /// .NET Task 41 | /// F# Async (FSharpAsync<Unit>) 42 | [] 43 | static member AsAsync (task: Task) = 44 | Infrastructures.asAsync (task, None) 45 | 46 | /// 47 | /// Seamless conversion from .NET Task to F# Async. 48 | /// 49 | /// .NET Task 50 | /// Cancellation token 51 | /// F# Async (FSharpAsync<Unit>) 52 | [] 53 | static member AsAsync (task: Task, token: CancellationToken) = 54 | Infrastructures.asAsync (task, Some token) 55 | 56 | /// 57 | /// Seamless conversion from .NET Task to F# Async. 58 | /// 59 | /// .NET Task 60 | /// True if continuation running on captured SynchronizationContext 61 | /// F# Async (FSharpAsync<Unit>) 62 | [] 63 | static member AsAsyncConfigured (task: Task, continueOnCapturedContext: bool) = 64 | Infrastructures.asAsyncCTA (ConfiguredTaskAsyncAwaitable(task.ConfigureAwait(continueOnCapturedContext))) 65 | 66 | /// 67 | /// Seamless conversion from .NET Task to F# Async. 68 | /// 69 | /// Computation result type 70 | /// .NET Task<'T> 71 | /// F# Async<'T> (FSharpAsync<'T>) 72 | [] 73 | static member AsAsync (task: Task<'T>) = 74 | Infrastructures.asAsyncT (task, None) 75 | 76 | /// 77 | /// Seamless conversion from .NET Task to F# Async. 78 | /// 79 | /// Computation result type 80 | /// .NET Task<'T> 81 | /// Cancellation token 82 | /// F# Async<'T> (FSharpAsync<'T>) 83 | [] 84 | static member AsAsync (task: Task<'T>, token: CancellationToken) = 85 | Infrastructures.asAsyncT (task, Some token) 86 | 87 | /// 88 | /// Seamless conversion from .NET Task to F# Async. 89 | /// 90 | /// Computation result type 91 | /// .NET Task<'T> 92 | /// True if continuation running on captured SynchronizationContext 93 | /// F# Async<'T> (FSharpAsync<'T>) 94 | [] 95 | static member AsAsyncConfigured (task: Task<'T>, continueOnCapturedContext: bool) = 96 | Infrastructures.asAsyncCTAT (ConfiguredTaskAsyncAwaitable<'T>(task.ConfigureAwait(continueOnCapturedContext))) 97 | 98 | /// 99 | /// Seamless conversion extensions in standard .NET ValueTask based infrastructure. 100 | /// 101 | [] 102 | type ValueTaskExtensions = 103 | 104 | /////////////////////////////////////////////////////////////////////////////////// 105 | // .NET (C#) side ValueTask --> Async conversion extensions. 106 | 107 | /// 108 | /// Seamless conversion from .NET ValueTask to F# Async. 109 | /// 110 | /// .NET ValueTask 111 | /// F# Async<Unit> (FSharpAsync<Unit>) 112 | [] 113 | static member AsAsync (task: ValueTask) = 114 | Infrastructures.asAsyncV (task, None) 115 | 116 | /// 117 | /// Seamless conversion from .NET ValueTask to F# Async. 118 | /// 119 | /// .NET ValueTask 120 | /// Cancellation token 121 | /// F# Async<Unit> (FSharpAsync<Unit>) 122 | [] 123 | static member AsAsync (task: ValueTask, token: CancellationToken) = 124 | Infrastructures.asAsyncV (task, Some token) 125 | 126 | /// 127 | /// Seamless conversion from .NET ValueTask to F# Async. 128 | /// 129 | /// .NET ValueTask 130 | /// True if continuation running on captured SynchronizationContext 131 | /// F# Async<Unit> (FSharpAsync<Unit>) 132 | [] 133 | static member AsAsyncConfigured (task: ValueTask, continueOnCapturedContext: bool) = 134 | Infrastructures.asAsyncCVTA (ConfiguredValueTaskAsyncAwaitable(task.ConfigureAwait(continueOnCapturedContext))) 135 | 136 | /// 137 | /// Seamless conversion from .NET ValueTask to F# Async. 138 | /// 139 | /// Computation result type 140 | /// .NET ValueTask<'T> 141 | /// F# Async<'T> (FSharpAsync<'T>) 142 | [] 143 | static member AsAsync (task: ValueTask<'T>) = 144 | Infrastructures.asAsyncVT (task, None) 145 | 146 | /// 147 | /// Seamless conversion from .NET ValueTask to F# Async. 148 | /// 149 | /// Computation result type 150 | /// .NET ValueTask<'T> 151 | /// Cancellation token 152 | /// F# Async<'T> (FSharpAsync<'T>) 153 | [] 154 | static member AsAsync (task: ValueTask<'T>, token: CancellationToken) = 155 | Infrastructures.asAsyncVT (task, Some token) 156 | 157 | /// 158 | /// Seamless conversion from .NET ValueTask to F# Async. 159 | /// 160 | /// Computation result type 161 | /// .NET ValueTask<'T> 162 | /// True if continuation running on captured SynchronizationContext 163 | /// F# Async<'T> (FSharpAsync<'T>) 164 | [] 165 | static member AsAsyncConfigured (task: ValueTask<'T>, continueOnCapturedContext: bool) = 166 | Infrastructures.asAsyncCVTAT (ConfiguredValueTaskAsyncAwaitable<'T>(task.ConfigureAwait(continueOnCapturedContext))) 167 | 168 | /////////////////////////////////////////////////////////////////////////////////// 169 | 170 | namespace Microsoft.FSharp.Control 171 | 172 | open System.Runtime.CompilerServices 173 | open System.Threading 174 | open System.Threading.Tasks 175 | 176 | /// 177 | /// Seamless conversion extensions in standard .NET Task based infrastructure. 178 | /// 179 | [] 180 | type AsyncExtensions = 181 | 182 | /////////////////////////////////////////////////////////////////////////////////// 183 | // .NET (C#) side Async --> Task conversion extensions. 184 | 185 | /// 186 | /// Seamless conversion from F# Async to .NET Task. 187 | /// 188 | /// F# Async (FSharpAsync<Unit>) 189 | /// .NET Task 190 | [] 191 | static member AsTask (async: Async) = 192 | Infrastructures.asTaskT (async, None) :> Task 193 | 194 | /// 195 | /// Seamless conversion from F# Async to .NET Task. 196 | /// 197 | /// F# Async (FSharpAsync<Unit>) 198 | /// Cancellation token 199 | /// .NET Task 200 | [] 201 | static member AsTask (async: Async, token: CancellationToken) = 202 | Infrastructures.asTaskT (async, Some token) :> Task 203 | 204 | /// 205 | /// Seamless conversion from F# Async to .NET Task. 206 | /// 207 | /// Computation result type 208 | /// F# Async<'T> (FSharpAsync<'T>) 209 | /// .NET Task<'T> 210 | [] 211 | static member AsTask (async: Async<'T>) = 212 | Infrastructures.asTaskT (async, None) 213 | 214 | /// 215 | /// Seamless conversion from F# Async to .NET Task. 216 | /// 217 | /// Computation result type 218 | /// F# Async<'T> (FSharpAsync<'T>) 219 | /// Cancellation token 220 | /// .NET Task<'T> 221 | [] 222 | static member AsTask (async: Async<'T>, token: CancellationToken) = 223 | Infrastructures.asTaskT (async, Some token) 224 | 225 | /////////////////////////////////////////////////////////////////////////////////// 226 | // .NET (C#) side Async --> ValueTask conversion extensions. 227 | 228 | /// 229 | /// Seamless conversion from F# Async to .NET ValueTask. 230 | /// 231 | /// F# Async<unit> (FSharpAsync<unit>) 232 | /// .NET ValueTask 233 | [] 234 | static member AsValueTask (async: Async) = 235 | Infrastructures.asValueTask (async, None) 236 | 237 | /// 238 | /// Seamless conversion from F# Async to .NET ValueTask. 239 | /// 240 | /// F# Async<unit> (FSharpAsync<unit>) 241 | /// Cancellation token 242 | /// .NET ValueTask 243 | [] 244 | static member AsValueTask (async: Async, token: CancellationToken) = 245 | Infrastructures.asValueTask (async, Some token) 246 | 247 | /// 248 | /// Seamless conversion from F# Async to .NET ValueTask. 249 | /// 250 | /// Computation result type 251 | /// F# Async<'T> (FSharpAsync<'T>) 252 | /// .NET ValueTask<'T> 253 | [] 254 | static member AsValueTask (async: Async<'T>) = 255 | Infrastructures.asValueTaskT (async, None) 256 | 257 | /// 258 | /// Seamless conversion from F# Async to .NET ValueTask. 259 | /// 260 | /// Computation result type 261 | /// F# Async<'T> (FSharpAsync<'T>) 262 | /// Cancellation token 263 | /// .NET ValueTask<'T> 264 | [] 265 | static member AsValueTask (async: Async<'T>, token: CancellationToken) = 266 | Infrastructures.asValueTaskT (async, Some token) 267 | 268 | /////////////////////////////////////////////////////////////////////////////////// 269 | // .NET (C#) side Async configurable extensions. 270 | 271 | /// 272 | /// Seamless configuring context support for F# Async. 273 | /// 274 | /// F# Async (FSharpAsync<unit>) 275 | /// True if continuation running on captured SynchronizationContext 276 | /// .NET TaskAwaiter 277 | [] 278 | static member ConfigureAwait (async: Async, continueOnCapturedContext: bool) = 279 | let task = Infrastructures.asTaskT (async, None) :> Task 280 | ConfiguredTaskAsyncAwaitable(task.ConfigureAwait(continueOnCapturedContext)) 281 | 282 | /// 283 | /// Seamless configuring context support for F# Async. 284 | /// 285 | /// Computation result type 286 | /// F# Async<'T> (FSharpAsync<'T>) 287 | /// True if continuation running on captured SynchronizationContext 288 | /// .NET TaskAwaiter<'T> 289 | [] 290 | static member ConfigureAwait (async: Async<'T>, continueOnCapturedContext: bool) = 291 | let task = Infrastructures.asTaskT (async, None) 292 | ConfiguredTaskAsyncAwaitable<'T>(task.ConfigureAwait(continueOnCapturedContext)) 293 | 294 | /////////////////////////////////////////////////////////////////////////////////// 295 | // .NET (C#) side Async awaitabler extensions. 296 | 297 | /// 298 | /// Seamless awaiter support for F# Async. 299 | /// 300 | /// F# Async (FSharpAsync<unit>) 301 | /// .NET TaskAwaiter 302 | [] 303 | static member GetAwaiter (async: Async) = 304 | let task = Infrastructures.asTaskT (async, None) :> Task 305 | AsyncAwaiter(task.GetAwaiter()) 306 | 307 | /// 308 | /// Seamless awaiter support for F# Async. 309 | /// 310 | /// Computation result type 311 | /// F# Async<'T> (FSharpAsync<'T>) 312 | /// .NET TaskAwaiter<'T> 313 | [] 314 | static member GetAwaiter (async: Async<'T>) = 315 | let task = Infrastructures.asTaskT (async, None) 316 | AsyncAwaiter<'T>(task.GetAwaiter()) 317 | -------------------------------------------------------------------------------- /FSharp.Control.FusionTasks/Utilities.fs: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // FSharp.Control.FusionTasks - F# Async workflow <--> .NET Task easy seamless interoperability library. 4 | // Copyright (c) 2016-2021 Kouji Matsui (@kozy_kekyo, @kekyo2) 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | ///////////////////////////////////////////////////////////////////////////////////////////////// 19 | 20 | namespace Microsoft.FSharp.Control 21 | 22 | open System 23 | open System.Threading 24 | open System.Threading.Tasks 25 | 26 | /////////////////////////////////////////////////////////////////////////////////// 27 | // Utilities. 28 | 29 | module internal Utilities = 30 | 31 | let createCanceledException(token: CancellationToken option) = 32 | // TODO: Constructed stack traces. require? 33 | try 34 | match token with 35 | | Some t -> new OperationCanceledException(t) |> raise 36 | | None -> new OperationCanceledException() |> raise 37 | with :? OperationCanceledException as e -> e 38 | 39 | let getScheduler() = 40 | match SynchronizationContext.Current with 41 | | null -> TaskScheduler.Current 42 | | _ -> TaskScheduler.FromCurrentSynchronizationContext() 43 | 44 | let unwrap = function 45 | | Some t -> t 46 | | None -> Unchecked.defaultof 47 | -------------------------------------------------------------------------------- /Images/FSharp.Control.FusionTasks.100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kekyo/FSharp.Control.FusionTasks/3685f0a25d9ce02ff1d133a8a96b8cc6c85c4931/Images/FSharp.Control.FusionTasks.100.png -------------------------------------------------------------------------------- /Images/FSharp.Control.FusionTasks.128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kekyo/FSharp.Control.FusionTasks/3685f0a25d9ce02ff1d133a8a96b8cc6c85c4931/Images/FSharp.Control.FusionTasks.128.png -------------------------------------------------------------------------------- /Images/FSharp.Control.FusionTasks.design: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kekyo/FSharp.Control.FusionTasks/3685f0a25d9ce02ff1d133a8a96b8cc6c85c4931/Images/FSharp.Control.FusionTasks.design -------------------------------------------------------------------------------- /Images/linqpad5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kekyo/FSharp.Control.FusionTasks/3685f0a25d9ce02ff1d133a8a96b8cc6c85c4931/Images/linqpad5.png -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | FSharp.Control.FusionTasks - F# Async workflow <--> .NET Task easy seamless interoperability library. 2 | Copyright (c) 2016-2018 Kouji Matsui (@kozy_kekyo) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | -------------------------------------------------------------------------------- /NuGet.Config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # F# FusionTasks 2 | 3 | ![FusionTasks](https://raw.githubusercontent.com/kekyo/FSharp.Control.FusionTasks/master/Images/FSharp.Control.FusionTasks.128.png) 4 | 5 | ## Status 6 | 7 | | | main | devel | 8 | |:---|:--:|:--:| 9 | | NuGet Package | [![NuGet FusionTasks](https://img.shields.io/nuget/v/FSharp.Control.FusionTasks.svg?style=flat)](https://www.nuget.org/packages/FSharp.Control.FusionTasks) | | 10 | | Continuous integration | [![RelaxVersioner CI build (main)](https://github.com/kekyo/FSharp.Control.FusionTasks/workflows/.NET/badge.svg?branch=main)](https://github.com/kekyo/FSharp.Control.FusionTasks/actions) | [![RelaxVersioner CI build (main)](https://github.com/kekyo/FSharp.Control.FusionTasks/workflows/.NET/badge.svg?branch=devel)](https://github.com/kekyo/FSharp.Control.FusionTasks/actions) | 11 | 12 | ----- 13 | 14 | ## What is this? 15 | 16 | * F# Async workflow <--> .NET Task/ValueTask easy seamless interoperability library. 17 | * Sample code (F# side): 18 | 19 | ``` fsharp 20 | let asyncTest = async { 21 | use ms = new MemoryStream() 22 | 23 | // FusionTasks directly interpreted System.Threading.Tasks.Task class in F# async-workflow block. 24 | do! ms.WriteAsync(data, 0, data.Length) 25 | do ms.Position <- 0L 26 | 27 | // FusionTasks directly interpreted System.Threading.Tasks.Task class in F# async-workflow block. 28 | let! length = ms.ReadAsync(data2, 0, data2.Length) 29 | do length |> should equal data2.Length 30 | } 31 | ``` 32 | 33 | * Sample code (C# side): 34 | 35 | ``` csharp 36 | using System.Threading.Tasks; 37 | using Microsoft.FSharp.Control; 38 | 39 | public async Task AsyncTest(FSharpAsync asyncIntComp) 40 | { 41 | // FusionTasks simple usage F#'s Async direct awaitable. 42 | await FSharpAsync.Sleep(500); 43 | Console.WriteLine("Awaited F# async function (unit)."); 44 | 45 | // FusionTasks simple usage F#'s Async direct awaitable. 46 | var result = await asyncIntComp; 47 | Console.WriteLine("Awaited F# async function: Result=" + result); 48 | } 49 | ``` 50 | 51 | ----- 52 | 53 | ## Features 54 | 55 | * Easy interoperable .NET Task/ValueTask <--> F#'s Async. 56 | * F# async workflow block now supports directly .NET Task/ValueTask handle with let!, do! and use!. 57 | * .NET (C# async-await) now supports directly F#'s Async. 58 | * SyncronizationContext capture operation support (F#: AsyncConfigure method / .NET (C#) AsAsyncConfigured method) 59 | * .NET now supports standard asynchronous sequence called `IAsyncEnumerable`, FusionTasks supports it with `for` expression. 60 | 61 | ## Benefits 62 | 63 | * Easy interoperability, combination and relation standard .NET OSS packages using Task/ValueTask and F#'s Async. 64 | * F# 6.0/4.5 with .NET 6.0/5.0, .NET Core 3.0/2.0 (or higher), .NET Standard 1.6/2.0 and .NET Framework 4.5/4.6.1/4.8. 65 | * Ready to LINQPad 5. 66 | 67 | ----- 68 | 69 | ## Environments 70 | 71 | * F# 6.0 or higher/4.5 72 | * .NET 6.0 73 | * .NET 5.0 74 | * .NET Core 3.0/2.0 or higher 75 | * .NET Standard 1.6/2.0/2.1 76 | * .NET Framework 4.5/4.6.1/4.8 77 | 78 | Combination chart: 79 | 80 | | .NET BCL | F# | Details | 81 | |:----|:----|:----| 82 | | .NET 6.0 | F# 6.0 or higher | | 83 | | .NET 5.0 | F# 6.0 or higher | | 84 | | .NET Core 3.1, 3.0 | F# 6.0 or higher | (3.0 is deprecated) | 85 | | .NET Core 2.,2 2.1, 2.0 | F# 6.0 or higher | (2.0 is deprecated) | 86 | | .NET Standard 2.1, 2.0 | F# 6.0 or higher | | 87 | | .NET Standard 1.6 | F# 4.5 | | 88 | | .NET Framework 4.8, 4.6.1 | F# 6.0 or higher | | 89 | | .NET Framework 4.5 | F# 4.5 | | 90 | 91 | ----- 92 | 93 | ## How to use 94 | 95 | * Search NuGet package and install "FSharp.Control.FusionTasks". 96 | * F# use, autoopen'd namespace "FSharp.Control". "System.Threading.Tasks" is optional. 97 | * C# use, using namespace "System.Threading.Tasks". "Microsoft.FSharp.Control" is optional. 98 | 99 | ## Samples 100 | 101 | ### Basic async workflow: 102 | 103 | ``` fsharp 104 | let asyncTest = async { 105 | use ms = new MemoryStream() 106 | 107 | // FusionTasks directly interpreted System.Threading.Tasks.Task class in F# async-workflow block. 108 | // Sure, non-generic Task mapping to Async. 109 | do! ms.WriteAsync(data, 0, data.Length) 110 | do ms.Position <- 0L 111 | 112 | // FusionTasks directly interpreted System.Threading.Tasks.Task class in F# async-workflow block. 113 | // Standard usage, same as manually used Async.AwaitTask. 114 | let! length = ms.ReadAsync(data2, 0, data2.Length) 115 | do length |> should equal data2.Length 116 | } 117 | ``` 118 | 119 | ### Without async workflow: 120 | 121 | ``` fsharp 122 | use ms = new MemoryStream() 123 | 124 | // Manually conversion by an operator "Async.AsAsync" : Task --> Async<'T> 125 | let asy = ms.ReadAsync(data, 0, data.Length) |> Async.AsAsync 126 | let length = asy |> Async.RunSynchronosly 127 | ``` 128 | 129 | ### Without async workflow (CancellationToken): 130 | 131 | ``` fsharp 132 | use ms = new MemoryStream() 133 | let cts = new CancellationTokenSource() 134 | 135 | // Produce with CancellationToken: 136 | // TIPS: FusionTasks cannot handle directly CancellationToken IN ASYNC WORKFLOW. 137 | // Because async workflow semantics implicitly handled CancellationToken with Async.DefaultCancellationToken, CancellationToken and CancelDefaultToken(). 138 | // (CancellationToken derived from Async.StartWithContinuations() in async workflow.) 139 | let asy = ms.ReadAsync(data, 0, data.Length).AsAsync(cts.Token) 140 | let length = asy |> Async.RunSynchronosly 141 | ``` 142 | 143 | ### Handle Task.ConfigureAwait(...) (Capture/release SynchContext): 144 | 145 | ``` fsharp 146 | let asyncTest = async { 147 | use ms = new MemoryStream(...) 148 | 149 | // We can use ConfigureAwait() on let!/do!. 150 | let! length = ms.ReadAsync(data, 0, data.Length).ConfigureAwait(false) 151 | } 152 | ``` 153 | 154 | NOTE: Older released contains `AsyncConfigure(bool)` method, but it was obsoleted. 155 | Because it existed for avoiding PCL strange linking errors. 156 | 157 | ### Delegate async continuation - works like TaskCompletionSource<T>: 158 | 159 | ``` fsharp 160 | open System.Threading 161 | 162 | let asyncCalculate() = 163 | // Create AsyncCompletionSource<'T>. 164 | let acs = new AsyncCompletionSource() 165 | 166 | // Execution with completely independent another thread... 167 | let thread = new Thread(new ThreadStart(fun _ -> 168 | Thread.Sleep(5000) 169 | // If you captured thread context (normally continuation or callbacks), 170 | // can delegation async continuation using AsyncCompletionSource<'T>. 171 | acs.SetResult(123 * 456))) 172 | thread.Start() 173 | 174 | // Async<'T> instance 175 | acs.Async 176 | ``` 177 | 178 | ### Standard asynchronous sequence IAsyncEnumerable<T>: 179 | 180 | ``` fsharp 181 | let asyncTest = async { 182 | // FusionTasks directly interpreted System.Collection.Generic.IAsyncEnumerable in 183 | // F# async-workflow for expression. 184 | for value in FooBarAccessor.EnumerableAsync() do 185 | // Totally asynchronous operation in each asynchronous iteration: 186 | let! result = value |> FooBarCollector.calculate 187 | do! output.WriteAsync(result) 188 | 189 | // ... (Continuation is asynchronously behind `for` loop) 190 | } 191 | ``` 192 | 193 | And, we can use `IAsyncEnumerable.ConfigureAwait(bool)` on it. 194 | 195 | NOTE: `IAsyncEnumerable` is supported only these environments: 196 | 197 | * net461 or higher. 198 | * netstandard2.0 or higher. 199 | * netcoreapp2.1 or higher. 200 | 201 | It limitation comes from [NuGet Microsoft.Bcl.AsyncInterfaces 5.0.0.](https://www.nuget.org/packages/Microsoft.Bcl.AsyncInterfaces/) 202 | 203 | ### Standard asynchronous disposer IAsyncDisposable: 204 | 205 | ``` fsharp 206 | let asyncTest = async { 207 | // FusionTasks directly interpreted System.IAsyncDisposable in 208 | // F# async-workflow use expression. 209 | // TIP: We can use `use` expression instead of `use!`, 210 | // Because the `use!` will be bound asynchronously BEFORE calling `DisposeAsync()`. 211 | use accessor = DatabaseAccessor.getAsyncDisposableAccessor() 212 | 213 | // (Use accessor...) 214 | 215 | // (Will be disposed asynchronously, calls `DisposeAsync()` at end of scope...) 216 | } 217 | ``` 218 | 219 | ### TIPS: We have to add annotation for arguments if using it in async workflow: 220 | 221 | ``` fsharp 222 | let asyncInner arg0 = async { 223 | // Cause FS0041: 224 | // A unique overload for method 'Source' could not be determined based on type information prior to this program point. 225 | // A type annotation may be needed. 226 | // --> Because F# compiler conflict arg0 type inferences: Async or Task. 227 | let! result = arg0 228 | let calculated = result + 1 229 | printfn "%d" calculated 230 | } 231 | 232 | // Fixed with type annotation Async<'T> or Task<'T>: 233 | let asyncInner (arg0:Async<_>) = async { 234 | let! result = arg0 235 | let calculated = result + 1 236 | printfn "%d" calculated 237 | } 238 | ``` 239 | 240 | ### In C# side: 241 | 242 | * Really need sample codes? huh? :) 243 | 244 | ----- 245 | 246 | ### Easy LINQPad 5 driven: 247 | 248 | * Before setup NuGet package (FSharp.Control.FusionTasks) the LINQPad NuGet Manager. 249 | 250 | ``` fsharp 251 | open System.IO 252 | 253 | // Result is Async 254 | let asyncSequenceData = 255 | let r = new Random() 256 | let data = [| for i = 1 to 100 do yield byte (r.Next()) |] 257 | async { 258 | use fs = new MemoryStream() 259 | do! fs.WriteAsync(data, 0, data.Length) 260 | do! fs.FlushAsync() 261 | return fs.ToArray() 262 | } 263 | 264 | // Convert to Task and dump: 265 | asyncSequenceData.AsTask().Dump() 266 | ``` 267 | 268 | ![LINQPad 5 driven](https://raw.githubusercontent.com/kekyo/FSharp.Control.FusionTasks/master/Images/linqpad5.png) 269 | 270 | ----- 271 | 272 | ## "task-like" and ValueTask appendix 273 | 274 | * .NET add new "task-like" type. "task-like" means applied a attribute "System.Runtime.CompilerServices.AsyncMethodBuilderAttribute" and declared the async method builder. 275 | * ValueTask overview: 276 | * New standard "task-like" type named for "ValueTask<T>" for C#. FusionTasks supported ValueTask<T> on 1.0.20. 277 | * ValueTask<T> declared by struct (Value type) for goal is improvement performance. But this type has the Task<T> instance inside and finally continuation handle by Task<T>. 278 | * ValueTask<T> performance effective situation maybe chatty-call fragments using both caller C# and awaiter C# code... 279 | * ValueTask<T> a little bit or no effect improvement performance, because usage of senario for FusionTasks. 280 | * "task-like" augumenting is difficult: 281 | * We have to apply to task-like type with the attribute "AsyncMethodBuilderAttribute". 282 | * Means if already declared type (Sure, we have FSharpAsync<'T>) cannot augument and cannot turn to task-like type. 283 | * Therefore cannot directly return for FSharpAsync<'T> from C#'s async-await method. 284 | * And cannot auto handle task-like type by FusionTasks, because no type safe declaration for task-like type... 285 | * For example, if force support task-like type, FusionTasks require augument "Source: taskLike: obj -> FSharpAsync<'T>" overload on FSharpAsync<'T>. This cannot type safe. 286 | * Conclusion: 287 | * So FusionTasks support only "ValueTask<T>" type and cannot support any other "task-like" types. 288 | 289 | ## Additional resources 290 | 291 | * Source codes available only "FSharp.Control.FusionTasks" folder. 292 | * The slides: "How to meets Async and Task" in Seattle F# Users group "MVP Summit Special: A Night of Lightning Talks" 2016.11.09 http://www.slideshare.net/kekyo/asyncs-vs-tasks 293 | 294 | 295 | 296 | ## TODO 297 | 298 | Improvements more easier/effective interfaces. 299 | 300 | ----- 301 | 302 | ## License 303 | 304 | * Copyright (c) 2016-2022 Kouji Matsui (@kozy_kekyo) 305 | * Under Apache v2 http://www.apache.org/licenses/LICENSE-2.0 306 | 307 | ## History 308 | 309 | * 2.6.0: 310 | * Final version. Thank you using FusionTasks! 311 | * You are ready to use both FusionTasks and F# 7.0! See [issue #14](https://github.com/kekyo/FSharp.Control.FusionTasks/issues/14). 312 | * Added supporting .NET Core 2.2 environments. 313 | * Minimized package dependency. 314 | * 2.5.0: 315 | * Supported .NET 6.0 and F# 6.0 environment. 316 | * 2.4.0: 317 | * Supported varies for operator `Async.AsAsync`. 318 | * Completed supporting configured capturing context method `ConfigureAwait(bool)` on all Task based instances. 319 | * 2.3.3: 320 | * Supported .NET asynchronous disposer (`IAsyncDisposable`). 321 | * Supported releasing synchronization context by `IAsyncEnumerable.ConfigureAwait(bool)`. 322 | * Fixed minor exception leaking at the continuation for asynchronous sequence. 323 | * 2.3.0: 324 | * Supported .NET asynchronous sequence (`IAsyncEnumerable` and essential types). 325 | * 2.2.0: 326 | * Suppressed Task/ValueTask allocation when they were already completed (#12, @danielmarbach) 327 | * 2.1.1: 328 | * Downgraded FSharp.Core requirements from 5.0.1 to 5.0.0. 329 | * 2.1.0: 330 | * Added .NET 5, .NET Core 3 and .NET Framework 4.8 assemblies. 331 | * Fixed capturing synchronization context at the asynchronous continuations. 332 | * 2.0.2: 333 | * Fixed add xml comments into package. 334 | * 2.0.1: 335 | * Add support ValueTask for non-generic version. 336 | * Fixed XML comments. 337 | * 2.0.0: 338 | * Supported F# 4.5, .NET Standard 2.0 and .NET Core 2.0. 339 | * Sorry, archived all PCL's libraries. Now FusionTasks supports only .NET Framework 4.5, .NET Core 2.0 and .NET Standard 1.6/2.0. 340 | * Solution structure refablished. Changed to .NET Core modern-style. 341 | * 1.1.1: 342 | * Add ValueTask<'T> bind source overload. 343 | * 1.1.0: 344 | * Supported F# 4.1 and .NET Standard 1.6. (Unfortunately deprecated FS40.netcore (netstandard1.4) package, try to migrate to F# 4.1 :) 345 | * 1.0.20: 346 | * Support ValueTask<T> (Exclude net40 and Profile 47 platform, added dependency for System.Threading.Tasks.Extensions). 347 | * Update version for .NET Core F# (1.0.0-alpha-161205). 348 | * 1.0.13: 349 | * Reduce to only contains .NET Core's assembly in FS40.netcore package. 350 | * Refactor folder structures. 351 | * 1.0.12: 352 | * Add .NET Core support (Separated package: FSharp.Control.FusionTasks.FS40.netcore with -Pre option required) 353 | * 1.0.2: 354 | * Support 'for .. in' expressions. (Thx Armin!) 355 | * 1.0.1: 356 | * Fixed cause undefined Async<'T> using combination Async<'T> and Task/Task<T> in async workflow. (Thx Honza!) 357 | * 1.0.0: 358 | * RTM release :clap: 359 | * Add FSharp.Core NuGet references. 360 | * Temporary disable support .NET Core. If reached F# RTM, continue development... (PR welcome!!) 361 | * Add sample codes. 362 | * 0.9.6: 363 | * WIP release. 364 | * 0.9.5: 365 | * WIP release. 366 | * 0.9.4: 367 | * Fixed nuspec reference System, System.Core 368 | * 0.9.3: 369 | * Fixed nuspec frameworkAssemblies. 370 | * 0.9.2: 371 | * Add package targetFramework. 372 | * Updated RelaxVersioner. 373 | * 0.9.1: 374 | * Remove strongly-signed (Unit test doesn't work...) 375 | * Omit synchronizers (AsyncLock, AsyncLazy). Thats moving to FSharp.Control.AsyncPrimitives project (https://github.com/kekyo/FSharp.Control.AsyncPrimitives). 376 | * Add target dnxcore50 into F# 4.0 (for .NET Core 1.0) 377 | * Source codes and documents bit changed. 378 | * 0.5.8: 379 | * Add strongly-signed. 380 | * 0.5.7: 381 | * Add PCL Profile 7. 382 | * 0.5.6: 383 | * Add PCL Profile 78. 384 | * Fixed minor PCL moniker fragments. 385 | * 0.5.5: 386 | * Fixed version number. 387 | * Fixed icon image url. 388 | * 0.5.4: 389 | * Auto open FSharp.Control. 390 | * Manage AppVeyor CI. 391 | * 0.5.3: Implement awaiter classes. 392 | * 0.5.2: Add dependency assemblies. 393 | * 0.5.1: NuGet package support. 394 | -------------------------------------------------------------------------------- /build-nupkg.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem FSharp.Control.FusionTasks - F# Async workflow <--> .NET Task easy seamless interoperability library. 4 | rem Copyright (c) 2016-2018 Kouji Matsui (@kozy_kekyo) 5 | rem 6 | rem Licensed under the Apache License, Version 2.0 (the "License"); 7 | rem you may not use this file except in compliance with the License. 8 | rem You may obtain a copy of the License at 9 | rem 10 | rem http://www.apache.org/licenses/LICENSE-2.0 11 | rem 12 | rem Unless required by applicable law or agreed to in writing, software 13 | rem distributed under the License is distributed on an "AS IS" BASIS, 14 | rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | rem See the License for the specific language governing permissions and 16 | rem limitations under the License. 17 | 18 | dotnet pack --configuration Release --output artifacts FSharp.Control.FusionTasks\FSharp.Control.FusionTasks.fsproj 19 | -------------------------------------------------------------------------------- /build-nupkg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # FSharp.Control.FusionTasks - F# Async workflow <--> .NET Task easy seamless interoperability library. 4 | # Copyright (c) 2016-2021 Kouji Matsui (@kozy_kekyo) 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | 18 | dotnet pack --configuration Release --output artifacts FSharp.Control.FusionTasks/FSharp.Control.FusionTasks.fsproj 19 | --------------------------------------------------------------------------------