├── .gitignore ├── LICENSE ├── README.md └── src ├── AsyncEnumerator.sln ├── AsyncEnumerator ├── AsyncEnumerator.cs ├── AsyncEnumerator.csproj ├── AsyncSequence.cs ├── CoopTask.cs ├── ForeachExtensions.cs ├── IAsyncEnumerator.cs ├── ITaskLike.cs ├── ITaskProviderAwaiter.cs ├── Properties │ └── AssemblyInfo.cs ├── TaskLikeBase.cs ├── TaskLikeObservable.cs ├── TaskProvider.cs └── packages.config ├── AsyncEnumeratorExamples ├── App.config ├── AsyncEnumeratorExamples.csproj ├── Program.cs ├── Properties │ └── AssemblyInfo.cs └── packages.config └── AsyncEnumeratorTests ├── AsyncEnumeratorTests.cs ├── AsyncEnumeratorTests.csproj ├── AsyncSequenceTests.cs ├── CoopTaskTests.cs ├── Properties └── AssemblyInfo.cs ├── TaskLikeObservableTests.cs └── packages.config /.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 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | *.VC.VC.opendb 85 | 86 | # Visual Studio profiler 87 | *.psess 88 | *.vsp 89 | *.vspx 90 | *.sap 91 | 92 | # TFS 2012 Local Workspace 93 | $tf/ 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | *.DotSettings.user 102 | 103 | # JustCode is a .NET coding add-in 104 | .JustCode 105 | 106 | # TeamCity is a build add-in 107 | _TeamCity* 108 | 109 | # DotCover is a Code Coverage Tool 110 | *.dotCover 111 | 112 | # NCrunch 113 | _NCrunch_* 114 | .*crunch*.local.xml 115 | nCrunchTemp_* 116 | 117 | # MightyMoose 118 | *.mm.* 119 | AutoTest.Net/ 120 | 121 | # Web workbench (sass) 122 | .sass-cache/ 123 | 124 | # Installshield output folder 125 | [Ee]xpress/ 126 | 127 | # DocProject is a documentation generator add-in 128 | DocProject/buildhelp/ 129 | DocProject/Help/*.HxT 130 | DocProject/Help/*.HxC 131 | DocProject/Help/*.hhc 132 | DocProject/Help/*.hhk 133 | DocProject/Help/*.hhp 134 | DocProject/Help/Html2 135 | DocProject/Help/html 136 | 137 | # Click-Once directory 138 | publish/ 139 | 140 | # Publish Web Output 141 | *.[Pp]ublish.xml 142 | *.azurePubxml 143 | # TODO: Comment the next line if you want to checkin your web deploy settings 144 | # but database connection strings (with potential passwords) will be unencrypted 145 | *.pubxml 146 | *.publishproj 147 | 148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 149 | # checkin your Azure Web App publish settings, but sensitive information contained 150 | # in these scripts will be unencrypted 151 | PublishScripts/ 152 | 153 | # NuGet Packages 154 | *.nupkg 155 | # The packages folder can be ignored because of Package Restore 156 | **/packages/* 157 | # except build/, which is used as an MSBuild target. 158 | !**/packages/build/ 159 | # Uncomment if necessary however generally it will be regenerated when needed 160 | #!**/packages/repositories.config 161 | # NuGet v3's project.json files produces more ignoreable files 162 | *.nuget.props 163 | *.nuget.targets 164 | 165 | # Microsoft Azure Build Output 166 | csx/ 167 | *.build.csdef 168 | 169 | # Microsoft Azure Emulator 170 | ecf/ 171 | rcf/ 172 | 173 | # Windows Store app package directories and files 174 | AppPackages/ 175 | BundleArtifacts/ 176 | Package.StoreAssociation.xml 177 | _pkginfo.txt 178 | 179 | # Visual Studio cache files 180 | # files ending in .cache can be ignored 181 | *.[Cc]ache 182 | # but keep track of directories ending in .cache 183 | !*.[Cc]ache/ 184 | 185 | # Others 186 | ClientBin/ 187 | ~$* 188 | *~ 189 | *.dbmdl 190 | *.dbproj.schemaview 191 | *.pfx 192 | *.publishsettings 193 | node_modules/ 194 | orleans.codegen.cs 195 | 196 | # Since there are multiple workflows, uncomment next line to ignore bower_components 197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 198 | #bower_components/ 199 | 200 | # RIA/Silverlight projects 201 | Generated_Code/ 202 | 203 | # Backup & report files from converting an old project file 204 | # to a newer Visual Studio version. Backup files are not needed, 205 | # because we have git ;-) 206 | _UpgradeReport_Files/ 207 | Backup*/ 208 | UpgradeLog*.XML 209 | UpgradeLog*.htm 210 | 211 | # SQL Server files 212 | *.mdf 213 | *.ldf 214 | 215 | # Business Intelligence projects 216 | *.rdl.data 217 | *.bim.layout 218 | *.bim_*.settings 219 | 220 | # Microsoft Fakes 221 | FakesAssemblies/ 222 | 223 | # GhostDoc plugin setting file 224 | *.GhostDoc.xml 225 | 226 | # Node.js Tools for Visual Studio 227 | .ntvs_analysis.dat 228 | 229 | # Visual Studio 6 build log 230 | *.plg 231 | 232 | # Visual Studio 6 workspace options file 233 | *.opt 234 | 235 | # Visual Studio LightSwitch build output 236 | **/*.HTMLClient/GeneratedArtifacts 237 | **/*.DesktopClient/GeneratedArtifacts 238 | **/*.DesktopClient/ModelManifest.xml 239 | **/*.Server/GeneratedArtifacts 240 | **/*.Server/ModelManifest.xml 241 | _Pvt_Extensions 242 | 243 | # Paket dependency manager 244 | .paket/paket.exe 245 | paket-files/ 246 | 247 | # FAKE - F# Make 248 | .fake/ 249 | 250 | # JetBrains Rider 251 | .idea/ 252 | *.sln.iml 253 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Andrew Hanlon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Note** C# 8 is out now and with it `IAsyncEnumerable`. That said, this repo can still stand as an example for implementing Task-Like types. Cheers! 2 | 3 | # Task-like Async Enumerators 4 | ## _a.k.a Abusing Task-like Types in C# 7_ 5 | 6 | Since the release of async/await, there has been a common desire for an 'async iterator' approach that could blend both the yield and async syntaxes. 7 | 8 | While there are several options currently available for asynchronous sequences (Rx Observables, DataFlow blocks), none have the concise beauty of async/await and yield iterators. 9 | 10 | Async sequences are on the book for C# 8, and there are a number of [interesting discussions](https://github.com/dotnet/roslyn/issues/261) happening in the Rosyln issues. 11 | 12 | But why wait - with C# 7's new Task-Like types we finally have the possibility to create async methods that return custom types. By capturing the underlying task-like object from within the method itself, we can return values _before_ the task has completed. 13 | 14 | Mads Torgersen has repeated that very few people will create Task-Like types, but I believe the approach shown here will have many cool applications. This repository contains my initial proof of concept. 15 | 16 | > ### Warning! 17 | > I put this together as an experiment, there are likely better ways to approach most of the inner workings. I put zero emphasis on correctness, safety, or performance. 18 | 19 | ### Details 20 | 21 | This repository contains several Task-like types that allow both cooporative and parallel async iterator methods. 22 | 23 | #### AsyncEnumerator<T> 24 | 25 | The `AsyncEnumerator` class provides behavior similar to a standard yield iterator method except that it allows for asynchronous operations. Each `yield.Return(T)` call returns a value and asynchronously waits for the next call to `MoveNextAsync()`: 26 | 27 | ``````````` c# 28 | 29 | public static async AsyncEnumerator Producer() 30 | { 31 | var yield = await AsyncEnumerator.Capture(); // Capture the underlying 'Task' 32 | 33 | await yield.Pause(); // Optionally wait for the first MoveNext call 34 | 35 | await yield.Return(1); // Yield the value and await MoveNext 36 | 37 | await Task.Delay(100); // Use any async constructs 38 | 39 | await yield.Return(2); 40 | 41 | return yield.Break(); // Return false to awaiting MoveNext 42 | } 43 | 44 | public static async Task Consumer() 45 | { 46 | var p = Producer(); 47 | 48 | while (await p.MoveNextAsync()) // Await the next value 49 | { 50 | Console.Write(" " + p.Current); // Use the current value 51 | } 52 | } 53 | 54 | ```````````` 55 | #### AsyncSequence<T> 56 | 57 | While cooperative iteration is great, I believe the more common desire with async code would be to have the producer run in parallel and simply await the availability of results (closer in concept to observables). The `AsyncSequence` class shown below accomplishes this goal: 58 | 59 | ``````````` c# 60 | 61 | public static async AsyncSequence Producer2() 62 | { 63 | var seq = await AsyncSequence.Capture(); // Capture the underlying 'Task' 64 | 65 | var users = await GetUsersAsync().ConfigureAwait(false); // Use any async constructs 66 | 67 | foreach(var user in users) 68 | { 69 | var fiends = await user.GetFriendsAsync(); // Build async 'flows' naturally 70 | 71 | seq.Return(friends.Count); // Signal an awaiting 72 | } // MoveNext, or queue the result. 73 | 74 | return seq.Break(); // Complete the sequence and 75 | } // return false to an awaiting MoveNext 76 | 77 | public static async Task Consumer2() 78 | { 79 | var p = Producer2(); 80 | 81 | while (await p.MoveNextAsync()) // Await the next value 82 | { 83 | Console.WriteLine(p.Current); // Use the current value 84 | } 85 | } 86 | 87 | ``````````````` 88 | 89 | #### Other Types 90 | 91 | I also threw together several additional types with a similar approach. One is a `TaskLikeObservable` which allows you to write a _flat_ and async `IObservable` method such as: 92 | 93 | `````````````` c# 94 | 95 | public static async TaskLikeObservable Producer() 96 | { 97 | var o = await TaskLikeObservable.Capture(); // Capture the underlying Task-like Obserable 98 | 99 | await o.Subscription; // wait for a subscriber 100 | 101 | for (var i = 0; i < 10; i++) 102 | { 103 | await Task.Delay(100).ConfigureAwait(false); // Use normal async constructs 104 | 105 | o.OnNext("y" + i); // send the value 106 | } 107 | 108 | return o.OnCompleted(); // complete the observable and return. 109 | } 110 | 111 | ... 112 | 113 | Producer().Subscribe(i => DoSomethingWith(i)); // Run and subscribe to the method 114 | 115 | ``````````````` 116 | 117 | Lastly I threw in a `CoopTask` class which allows a parent and child task to pass control back and forth similarly to a yielding enumerator: 118 | 119 | ``````````` c# 120 | 121 | public static async CoopTask Child() 122 | { 123 | var task = await CoopTask.Capture(); // Capture the underlying 'Task' 124 | 125 | Console.WriteLine("C0"); 126 | 127 | await task.Yield(); // Yield control back to parent 128 | 129 | await Task.Delay(100).ConfigureAwait(false); // Use any async constructs 130 | 131 | Console.WriteLine("C1"); 132 | 133 | await task.Yield(); // Yield control 134 | 135 | await Task.Delay(100); 136 | 137 | Console.WriteLine("C2"); 138 | 139 | await task.Break(); // Mark the task as completed 140 | 141 | Console.WriteLine("C3"); // Will not be run. 142 | } 143 | 144 | public static async Task Parent() 145 | { 146 | var c = Child(); 147 | 148 | var i = 1; 149 | 150 | while (await c.MoveNextAsync()) // Await the next child operation 151 | { 152 | Console.WriteLine("P" + i++); 153 | } 154 | } 155 | 156 | ```````````` 157 | 158 | ## Discussion 159 | 160 | There are likely better ways of implementing most of the internals of these types. For one thing, I largely used classes over structs (which are used by the standard framework types) to allow for more code reuse. 161 | 162 | The 'hack' for capturing the underlying Task-like object is not as bad as I thought it would have to be - I originally used reflection, then found the current method which only relies on a 'dummy' continuation. 163 | 164 | The main oddity I have run into is that the compiler appears to treat all generic Task-like methods as if they should behave like `Task` even when their builder semantics actually follow the void returning `Task` approach. This means you have to 'return' a value even when the _Task_ and awaiter have no `Result` field. I guess this wasn't the intended usage... 165 | 166 | I will be very curious to hear other developer's feedback. Please leave comments or feedback as issues. And of course, I welcome any pull requests for better code or more functionality. 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /src/AsyncEnumerator.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26403.3 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsyncEnumerator", "AsyncEnumerator\AsyncEnumerator.csproj", "{72BD723B-82B2-41A4-972D-E8CF56A6F239}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsyncEnumeratorTests", "AsyncEnumeratorTests\AsyncEnumeratorTests.csproj", "{57EC6540-AF1C-4A5E-884B-79A8A0FDCA96}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsyncEnumeratorExamples", "AsyncEnumeratorExamples\AsyncEnumeratorExamples.csproj", "{6571283F-D632-4118-98F1-926747F87193}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {72BD723B-82B2-41A4-972D-E8CF56A6F239}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {72BD723B-82B2-41A4-972D-E8CF56A6F239}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {72BD723B-82B2-41A4-972D-E8CF56A6F239}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {72BD723B-82B2-41A4-972D-E8CF56A6F239}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {57EC6540-AF1C-4A5E-884B-79A8A0FDCA96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {57EC6540-AF1C-4A5E-884B-79A8A0FDCA96}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {57EC6540-AF1C-4A5E-884B-79A8A0FDCA96}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {57EC6540-AF1C-4A5E-884B-79A8A0FDCA96}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {6571283F-D632-4118-98F1-926747F87193}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {6571283F-D632-4118-98F1-926747F87193}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {6571283F-D632-4118-98F1-926747F87193}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {6571283F-D632-4118-98F1-926747F87193}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | EndGlobal 35 | -------------------------------------------------------------------------------- /src/AsyncEnumerator/AsyncEnumerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.ExceptionServices; 4 | using System.Threading.Tasks; 5 | 6 | namespace AsyncEnumerator 7 | { 8 | public interface IAsyncEnumeratorProducer 9 | { 10 | T Break(); 11 | Task Pause(); 12 | Task Return(T value); 13 | } 14 | 15 | [AsyncMethodBuilder(typeof(AsyncEnumeratorMethodBuilder<>))] 16 | public class AsyncEnumerator : TaskLikeBase, IAsyncEnumeratorProducer, IAsyncEnumerator 17 | { 18 | private ExceptionDispatchInfo _exception; 19 | 20 | private bool _isStarted; 21 | private TaskCompletionSource _nextSource; 22 | private TaskCompletionSource _yieldSource; 23 | 24 | public static TaskProvider> Capture() => TaskProvider>.Instance; 25 | 26 | public T Current { get; internal set; } 27 | 28 | public Task MoveNextAsync() 29 | { 30 | _exception?.Throw(); 31 | 32 | if (!_isStarted) 33 | { 34 | _isStarted = true; 35 | return Task.FromResult(true); 36 | } 37 | 38 | _nextSource = new TaskCompletionSource(); 39 | 40 | _yieldSource?.TrySetResult(true); 41 | 42 | return _yieldSource is null ? Task.FromResult(true) : _nextSource.Task; 43 | } 44 | 45 | internal override void SetException(ExceptionDispatchInfo exception) 46 | { 47 | _exception = exception; 48 | _nextSource?.TrySetException(exception.SourceException); 49 | } 50 | 51 | T IAsyncEnumeratorProducer.Break() 52 | { 53 | IsCompleted = true; 54 | _nextSource.TrySetResult(false); 55 | return default(T); 56 | } 57 | 58 | Task IAsyncEnumeratorProducer.Pause() 59 | { 60 | _isStarted = true; 61 | _yieldSource = new TaskCompletionSource(); 62 | return _yieldSource.Task; 63 | } 64 | 65 | Task IAsyncEnumeratorProducer.Return(T value) 66 | { 67 | Current = value; 68 | 69 | _yieldSource = new TaskCompletionSource(); 70 | 71 | _nextSource?.TrySetResult(true); 72 | 73 | return _yieldSource.Task; 74 | } 75 | } 76 | 77 | public class AsyncEnumeratorMethodBuilder 78 | { 79 | private AsyncTaskMethodBuilder _methodBuilder; 80 | 81 | public static AsyncEnumeratorMethodBuilder Create() => new AsyncEnumeratorMethodBuilder(new AsyncEnumerator()); 82 | 83 | internal AsyncEnumeratorMethodBuilder(AsyncEnumerator task) 84 | { 85 | _methodBuilder = AsyncTaskMethodBuilder.Create(); 86 | Task = task; 87 | } 88 | 89 | public AsyncEnumerator Task { get; } 90 | 91 | public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) 92 | where TAwaiter : INotifyCompletion 93 | where TStateMachine : IAsyncStateMachine 94 | { 95 | if ((INotifyCompletion) awaiter is TaskProvider>.TaskProviderAwaiter provider) 96 | provider.OnCompleted(((IAsyncStateMachine) stateMachine).MoveNext, Task); 97 | else 98 | _methodBuilder.AwaitOnCompleted(ref awaiter, ref stateMachine); 99 | } 100 | 101 | public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) 102 | where TAwaiter : ICriticalNotifyCompletion 103 | where TStateMachine : IAsyncStateMachine 104 | { 105 | _methodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); 106 | } 107 | 108 | public void SetException(Exception ex) => Task.SetException(ExceptionDispatchInfo.Capture(ex)); 109 | 110 | public void SetResult(T result) => Task.SetCompletion(); 111 | 112 | public void SetStateMachine(IAsyncStateMachine stateMachine) { } 113 | 114 | public void Start(ref TStateMachine stateMachine) 115 | where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext(); 116 | } 117 | } -------------------------------------------------------------------------------- /src/AsyncEnumerator/AsyncEnumerator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {72BD723B-82B2-41A4-972D-E8CF56A6F239} 8 | Library 9 | Properties 10 | AsyncEnumerator 11 | AsyncEnumerator 12 | v4.5.2 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | ..\packages\System.Reactive.Core.3.1.1\lib\net45\System.Reactive.Core.dll 37 | 38 | 39 | ..\packages\System.Reactive.Interfaces.3.1.1\lib\net45\System.Reactive.Interfaces.dll 40 | 41 | 42 | ..\packages\System.Reactive.Linq.3.1.1\lib\net45\System.Reactive.Linq.dll 43 | 44 | 45 | ..\packages\System.Reactive.PlatformServices.3.1.1\lib\net45\System.Reactive.PlatformServices.dll 46 | 47 | 48 | ..\packages\System.Reactive.Windows.Threading.3.1.1\lib\net45\System.Reactive.Windows.Threading.dll 49 | 50 | 51 | ..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/AsyncEnumerator/AsyncSequence.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.ExceptionServices; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace AsyncEnumerator 9 | { 10 | public interface IAsyncSequenceProducer 11 | { 12 | T Break(); 13 | void Return(T value); 14 | } 15 | 16 | [AsyncMethodBuilder(typeof(AsyncSequenceMethodBuilder<>))] 17 | public class AsyncSequence : TaskLikeBase, IAsyncSequenceProducer, IAsyncEnumerator 18 | { 19 | private readonly ConcurrentQueue _valueQueue = new ConcurrentQueue(); 20 | private ExceptionDispatchInfo _exception; 21 | 22 | private TaskCompletionSource _nextSource; 23 | 24 | public static TaskProvider> Capture() => TaskProvider>.Instance; 25 | 26 | public T Current { get; private set; } 27 | 28 | public async Task MoveNextAsync() 29 | { 30 | _exception?.Throw(); 31 | 32 | if (_valueQueue.TryDequeue(out var value)) 33 | { 34 | Current = value; 35 | return true; 36 | } 37 | 38 | if (IsCompleted) 39 | return false; 40 | 41 | _nextSource = new TaskCompletionSource(); 42 | 43 | await _nextSource.Task; 44 | 45 | if (!_valueQueue.TryDequeue(out value)) 46 | return !IsCompleted; 47 | 48 | Current = value; 49 | return true; 50 | } 51 | 52 | public async Task MoveNextAsync(CancellationToken cancellationToken) 53 | { 54 | _exception?.Throw(); 55 | 56 | if (_valueQueue.TryDequeue(out var value)) 57 | { 58 | Current = value; 59 | return true; 60 | } 61 | 62 | if (IsCompleted) 63 | return false; 64 | 65 | _nextSource = new TaskCompletionSource(); 66 | 67 | using (cancellationToken.Register(() => _nextSource.TrySetCanceled())) 68 | { 69 | await _nextSource.Task; 70 | } 71 | 72 | if (!_valueQueue.TryDequeue(out value)) 73 | return !IsCompleted; 74 | 75 | Current = value; 76 | return true; 77 | } 78 | 79 | /// 80 | /// A quick filter method. 81 | /// 82 | public async AsyncSequence Where(Func condition) 83 | { 84 | var seq = await AsyncSequence.Capture(); 85 | 86 | while (await MoveNextAsync()) 87 | { 88 | if(condition(Current)) 89 | { 90 | seq.Return(Current); 91 | } 92 | } 93 | 94 | return seq.Break(); 95 | } 96 | 97 | internal override void SetException(ExceptionDispatchInfo exception) 98 | { 99 | _exception = exception; 100 | _nextSource?.TrySetException(exception.SourceException); 101 | } 102 | 103 | T IAsyncSequenceProducer.Break() 104 | { 105 | IsCompleted = true; 106 | _nextSource?.TrySetResult(false); 107 | return default(T); 108 | } 109 | 110 | void IAsyncSequenceProducer.Return(T value) 111 | { 112 | _valueQueue.Enqueue(value); 113 | _nextSource?.TrySetResult(true); 114 | } 115 | } 116 | 117 | public struct AsyncSequenceMethodBuilder 118 | { 119 | private AsyncTaskMethodBuilder _methodBuilder; 120 | 121 | public static AsyncSequenceMethodBuilder Create() => new AsyncSequenceMethodBuilder(new AsyncSequence()); 122 | 123 | internal AsyncSequenceMethodBuilder(AsyncSequence task) 124 | { 125 | _methodBuilder = AsyncTaskMethodBuilder.Create(); 126 | Task = task; 127 | } 128 | 129 | public void SetStateMachine(IAsyncStateMachine stateMachine) { } 130 | 131 | public void Start(ref TStateMachine stateMachine) 132 | where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext(); 133 | 134 | public void SetException(Exception ex) => Task.SetException(ExceptionDispatchInfo.Capture(ex)); 135 | 136 | public void SetResult(T value) => Task.SetCompletion(); 137 | 138 | public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) 139 | where TAwaiter : INotifyCompletion 140 | where TStateMachine : IAsyncStateMachine 141 | { 142 | // The requirement for this cast is ridiculous. 143 | if ((INotifyCompletion) awaiter is TaskProvider>.TaskProviderAwaiter provider) 144 | provider.OnCompleted(((IAsyncStateMachine) stateMachine).MoveNext, Task); 145 | else 146 | _methodBuilder.AwaitOnCompleted(ref awaiter, ref stateMachine); 147 | } 148 | 149 | public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) 150 | where TAwaiter : ICriticalNotifyCompletion 151 | where TStateMachine : IAsyncStateMachine 152 | { 153 | _methodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); 154 | } 155 | 156 | public AsyncSequence Task { get; } 157 | } 158 | } -------------------------------------------------------------------------------- /src/AsyncEnumerator/CoopTask.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.ExceptionServices; 4 | using System.Threading.Tasks; 5 | 6 | namespace AsyncEnumerator 7 | { 8 | public interface ICoopTaskProducer 9 | { 10 | Task Break(); 11 | Task Yield(); 12 | } 13 | 14 | [AsyncMethodBuilder(typeof(CoopTaskMethodBuilder))] 15 | public class CoopTask : TaskLikeBase, ICoopTaskProducer, ITaskLike 16 | { 17 | private ExceptionDispatchInfo _exception; 18 | 19 | private bool _isStarted; 20 | private TaskCompletionSource _nextSource; 21 | private TaskCompletionSource _yieldSource; 22 | 23 | public static TaskProvider Capture() => TaskProvider.Instance; 24 | 25 | public override TaskLikeAwaiter GetAwaiter() 26 | { 27 | _exception?.Throw(); 28 | return new TaskLikeAwaiter(this); 29 | } 30 | 31 | public Task MoveNextAsync() 32 | { 33 | _exception?.Throw(); 34 | 35 | if (!_isStarted) 36 | { 37 | _isStarted = true; 38 | return Task.FromResult(true); 39 | } 40 | 41 | _nextSource = new TaskCompletionSource(); 42 | 43 | _yieldSource?.TrySetResult(true); 44 | 45 | return _yieldSource == null ? Task.FromResult(true) : _nextSource.Task; 46 | } 47 | 48 | internal override void SetCompletion() 49 | { 50 | IsCompleted = true; 51 | _nextSource.TrySetResult(false); 52 | } 53 | 54 | internal override void SetException(ExceptionDispatchInfo exception) 55 | { 56 | _exception = exception; 57 | _nextSource?.TrySetException(exception.SourceException); 58 | } 59 | 60 | Task ICoopTaskProducer.Break() 61 | { 62 | IsCompleted = true; 63 | _nextSource.TrySetResult(false); 64 | return new TaskCompletionSource().Task; 65 | } 66 | 67 | Task ICoopTaskProducer.Yield() 68 | { 69 | _yieldSource = new TaskCompletionSource(); 70 | 71 | _nextSource?.TrySetResult(true); 72 | 73 | return _yieldSource.Task; 74 | } 75 | } 76 | 77 | public struct CoopTaskMethodBuilder 78 | { 79 | private AsyncTaskMethodBuilder _methodBuilder; 80 | 81 | public static CoopTaskMethodBuilder Create() => new CoopTaskMethodBuilder(new CoopTask()); 82 | 83 | internal CoopTaskMethodBuilder(CoopTask task) 84 | { 85 | _methodBuilder = AsyncTaskMethodBuilder.Create(); 86 | Task = task; 87 | } 88 | 89 | public void SetStateMachine(IAsyncStateMachine stateMachine) { } 90 | 91 | public void Start(ref TStateMachine stateMachine) 92 | where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext(); 93 | 94 | public void SetException(Exception ex) => Task.SetException(ExceptionDispatchInfo.Capture(ex)); 95 | 96 | public void SetResult() => Task.SetCompletion(); 97 | 98 | public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) 99 | where TAwaiter : INotifyCompletion 100 | where TStateMachine : IAsyncStateMachine 101 | { 102 | if ((INotifyCompletion) awaiter is TaskProvider.TaskProviderAwaiter provider) 103 | provider.OnCompleted(((IAsyncStateMachine) stateMachine).MoveNext, Task); 104 | else 105 | _methodBuilder.AwaitOnCompleted(ref awaiter, ref stateMachine); 106 | } 107 | 108 | public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, 109 | ref TStateMachine stateMachine) 110 | where TAwaiter : ICriticalNotifyCompletion 111 | where TStateMachine : IAsyncStateMachine 112 | { 113 | _methodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); 114 | } 115 | 116 | public CoopTask Task { get; } 117 | } 118 | } -------------------------------------------------------------------------------- /src/AsyncEnumerator/ForeachExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace AsyncEnumerator 5 | { 6 | public static class ForeachExtensions 7 | { 8 | 9 | public static async Task ForeachAsync(this IAsyncEnumerator iter, Func action) 10 | { 11 | while (await iter.MoveNextAsync()) 12 | { 13 | await action(iter.Current); 14 | } 15 | } 16 | 17 | public static async Task ForeachAsync(this IAsyncEnumerator iter, Action action) 18 | { 19 | while (await iter.MoveNextAsync()) 20 | { 21 | action(iter.Current); 22 | } 23 | } 24 | 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /src/AsyncEnumerator/IAsyncEnumerator.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace AsyncEnumerator 5 | { 6 | public interface IAsyncEnumerator 7 | { 8 | T Current { get; } 9 | Task MoveNextAsync(); 10 | } 11 | } -------------------------------------------------------------------------------- /src/AsyncEnumerator/ITaskLike.cs: -------------------------------------------------------------------------------- 1 | namespace AsyncEnumerator 2 | { 3 | public interface ITaskLike 4 | { 5 | bool IsCompleted { get; } 6 | 7 | TaskLikeBase.TaskLikeAwaiter GetAwaiter(); 8 | } 9 | } -------------------------------------------------------------------------------- /src/AsyncEnumerator/ITaskProviderAwaiter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.CompilerServices; 5 | using System.Runtime.ExceptionServices; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace AsyncEnumerator 10 | { 11 | public interface ITaskProviderAwaiter : INotifyCompletion 12 | { 13 | bool IsCompleted { get; } 14 | void OnCompleted(Action continuation, ITaskLike asyncEnumerator); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/AsyncEnumerator/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("AsyncEnumerator")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("AsyncEnumerator")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("72bd723b-82b2-41a4-972d-e8cf56a6f239")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/AsyncEnumerator/TaskLikeBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.ExceptionServices; 4 | 5 | namespace AsyncEnumerator 6 | { 7 | public abstract class TaskLikeBase : ITaskLike 8 | { 9 | public bool IsCompleted { get; protected set; } 10 | 11 | public virtual TaskLikeAwaiter GetAwaiter() => new TaskLikeAwaiter(this); 12 | 13 | internal virtual void SetCompletion() => IsCompleted = true; 14 | 15 | internal abstract void SetException(ExceptionDispatchInfo exception); 16 | 17 | public class TaskLikeAwaiter : INotifyCompletion 18 | { 19 | private readonly ITaskLike _task; 20 | private TaskAwaiter _taskAwaiter; 21 | 22 | internal TaskLikeAwaiter(ITaskLike task) 23 | { 24 | _task = task; 25 | _taskAwaiter = new TaskAwaiter(); 26 | } 27 | 28 | public bool IsCompleted => _task.IsCompleted; 29 | 30 | public void GetResult() { } 31 | 32 | public void OnCompleted(Action a) { _taskAwaiter.OnCompleted(a); } 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/AsyncEnumerator/TaskLikeObservable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Subjects; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.ExceptionServices; 5 | using System.Threading.Tasks; 6 | 7 | namespace AsyncEnumerator 8 | { 9 | public interface ITaskLikeSubject 10 | { 11 | Task Subscription { get; } 12 | T OnCompleted(); 13 | void OnNext(T value); 14 | } 15 | 16 | [AsyncMethodBuilder(typeof(TaskLikeObservableMethodBuilder<>))] 17 | public class TaskLikeObservable : TaskLikeBase, IObservable, ITaskLikeSubject, ITaskLike 18 | { 19 | private readonly Subject _subject = new Subject(); 20 | 21 | private readonly TaskCompletionSource _subscribeTask = new TaskCompletionSource(); 22 | 23 | public static TaskProvider> Capture() => TaskProvider>.Instance; 24 | 25 | public Task Subscription => _subscribeTask.Task; 26 | 27 | internal override void SetException(ExceptionDispatchInfo exception) => _subject.OnError(exception.SourceException); 28 | 29 | public IDisposable Subscribe(IObserver observer) 30 | { 31 | var ret = _subject.Subscribe(observer); 32 | 33 | _subscribeTask.TrySetResult(true); 34 | 35 | return ret; 36 | } 37 | 38 | T ITaskLikeSubject.OnCompleted() 39 | { 40 | _subject.OnCompleted(); 41 | 42 | IsCompleted = true; 43 | 44 | return default(T); 45 | } 46 | 47 | void ITaskLikeSubject.OnNext(T value) { _subject.OnNext(value); } 48 | } 49 | 50 | public struct TaskLikeObservableMethodBuilder 51 | { 52 | private AsyncTaskMethodBuilder _methodBuilder; 53 | 54 | public static TaskLikeObservableMethodBuilder Create() => new TaskLikeObservableMethodBuilder(new TaskLikeObservable()); 55 | 56 | internal TaskLikeObservableMethodBuilder(TaskLikeObservable task) 57 | { 58 | _methodBuilder = AsyncTaskMethodBuilder.Create(); 59 | Task = task; 60 | } 61 | 62 | public void SetStateMachine(IAsyncStateMachine stateMachine) { } 63 | 64 | public void Start(ref TStateMachine stateMachine) 65 | where TStateMachine : IAsyncStateMachine => stateMachine.MoveNext(); 66 | 67 | public void SetException(Exception e) => Task.SetException(ExceptionDispatchInfo.Capture(e)); 68 | 69 | public void SetResult(T value) => Task.SetCompletion(); 70 | 71 | public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) 72 | where TAwaiter : INotifyCompletion 73 | where TStateMachine : IAsyncStateMachine 74 | { 75 | if ((INotifyCompletion) awaiter is TaskProvider>.TaskProviderAwaiter provider) 76 | provider.OnCompleted(((IAsyncStateMachine) stateMachine).MoveNext, Task); 77 | else 78 | _methodBuilder.AwaitOnCompleted(ref awaiter, ref stateMachine); 79 | } 80 | 81 | public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) 82 | where TAwaiter : ICriticalNotifyCompletion 83 | where TStateMachine : IAsyncStateMachine 84 | { 85 | _methodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); 86 | } 87 | 88 | public TaskLikeObservable Task { get; } 89 | } 90 | 91 | 92 | } -------------------------------------------------------------------------------- /src/AsyncEnumerator/TaskProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace AsyncEnumerator 6 | { 7 | public class TaskProvider 8 | { 9 | internal static readonly TaskProvider Instance = new TaskProvider(); 10 | 11 | public TaskProviderAwaiter GetAwaiter() { return new TaskProviderAwaiter(); } 12 | 13 | public class TaskProviderAwaiter : INotifyCompletion 14 | { 15 | private TOutput _task; 16 | 17 | public bool IsCompleted => _task != null; 18 | 19 | public TOutput GetResult() { return _task; } 20 | 21 | public void OnCompleted(Action continuation) { throw new InvalidOperationException("OnCompleted override with asyncEnumerator param must be called."); } 22 | 23 | public void OnCompleted(Action continuation, TInput task) where TInput : TOutput 24 | { 25 | _task = task; 26 | continuation(); 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/AsyncEnumerator/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/AsyncEnumeratorExamples/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/AsyncEnumeratorExamples/AsyncEnumeratorExamples.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {6571283F-D632-4118-98F1-926747F87193} 8 | Exe 9 | AsyncEnumeratorExamples 10 | AsyncEnumeratorExamples 11 | v4.5.2 12 | 512 13 | true 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 7 25 | 26 | 27 | AnyCPU 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | ..\packages\System.Reactive.Core.3.1.1\lib\net45\System.Reactive.Core.dll 40 | 41 | 42 | ..\packages\System.Reactive.Interfaces.3.1.1\lib\net45\System.Reactive.Interfaces.dll 43 | 44 | 45 | ..\packages\System.Reactive.Linq.3.1.1\lib\net45\System.Reactive.Linq.dll 46 | 47 | 48 | ..\packages\System.Reactive.PlatformServices.3.1.1\lib\net45\System.Reactive.PlatformServices.dll 49 | 50 | 51 | ..\packages\System.Reactive.Windows.Threading.3.1.1\lib\net45\System.Reactive.Windows.Threading.dll 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | {72BD723B-82B2-41A4-972D-E8CF56A6F239} 73 | AsyncEnumerator 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/AsyncEnumeratorExamples/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Diagnostics; 4 | using System.Threading.Tasks; 5 | using AsyncEnumerator; 6 | using System.Reactive.Linq; 7 | 8 | namespace AsyncEnumeratorExamples 9 | { 10 | internal class Program 11 | { 12 | private static void Main() 13 | { 14 | MainAsync().GetAwaiter().GetResult(); 15 | } 16 | 17 | public static async Task MainAsync() 18 | { 19 | Console.WriteLine("AsyncEnumerator:"); 20 | await Consumer(); 21 | 22 | Console.WriteLine("\nAsyncSequence:"); 23 | await Consumer2(); 24 | 25 | Console.WriteLine("\nTaskLikeObservable:"); 26 | await Consumer3(); 27 | 28 | Console.WriteLine("\nCoopTask:"); 29 | await Consumer4(); 30 | 31 | Console.WriteLine("\nCoopTask2:"); 32 | await Consumer5(); 33 | 34 | Console.WriteLine("\nPress any key to exit."); 35 | Console.ReadKey(true); 36 | } 37 | 38 | public static async AsyncEnumerator Producer() 39 | { 40 | var yield = await AsyncEnumerator.Capture(); // Capture the underlying 'Task' 41 | 42 | await yield.Pause(); // Optionally Wait for first MoveNext call 43 | 44 | await Task.Delay(100).ConfigureAwait(false); // Use any async constructs 45 | 46 | await yield.Return(1); // Yield a value and wait for MoveNext 47 | 48 | await Task.Delay(100); 49 | 50 | await yield.Return(2); 51 | 52 | return yield.Break(); // Return false to awaiting MoveNext 53 | } 54 | 55 | public static async Task Consumer() 56 | { 57 | var p = Producer(); 58 | 59 | while (await p.MoveNextAsync()) // Await the next value 60 | { 61 | Console.WriteLine(p.Current); // Use the current value 62 | } 63 | } 64 | 65 | 66 | public static async Task Consumer2() 67 | { 68 | var p = Producer2().Where(i => i % 2 == 0); 69 | 70 | while (await p.MoveNextAsync()) 71 | { 72 | Console.WriteLine(p.Current); 73 | } 74 | } 75 | 76 | public static async AsyncSequence Producer2() 77 | { 78 | var seq = await AsyncSequence.Capture(); // Capture the underlying 'Task' 79 | 80 | for (int i = 1; i <= 5; i++) 81 | { 82 | await Task.Delay(100).ConfigureAwait(false); // Use any async constructs 83 | 84 | seq.Return(i); // Return to an awaiting MoveNext, or queue the result. 85 | } 86 | 87 | return seq.Break(); // Returns false to awaiting MoveNext 88 | } 89 | 90 | public static Task Consumer3() 91 | { 92 | return Producer3().ForEachAsync(s => Console.WriteLine(s)); 93 | } 94 | 95 | public static async TaskLikeObservable Producer3() 96 | { 97 | var o = await TaskLikeObservable.Capture(); 98 | 99 | await o.Subscription; 100 | 101 | for (var i = 0; i < 10; i++) 102 | { 103 | o.OnNext("y" + i); 104 | 105 | await Task.Delay(100).ConfigureAwait(false); 106 | } 107 | 108 | return o.OnCompleted(); 109 | } 110 | 111 | public static async CoopTask Producer4() 112 | { 113 | var task = await CoopTask.Capture(); // Capture the underlying 'Task' 114 | 115 | Console.WriteLine("P0"); 116 | 117 | await task.Yield(); // Yield control back to parent 118 | 119 | await Task.Delay(100).ConfigureAwait(false); // Use any async constructs 120 | 121 | Console.WriteLine("P1"); 122 | 123 | await task.Yield(); // Yield control 124 | 125 | await Task.Delay(100); 126 | 127 | Console.WriteLine("P2"); 128 | 129 | await task.Break(); // Mark the task as completed 130 | 131 | Console.WriteLine("P3"); // Will not be run. 132 | } 133 | 134 | public static async Task Consumer4() 135 | { 136 | var p = Producer4(); 137 | 138 | var i = 1; 139 | 140 | while (await p.MoveNextAsync()) // Await the next child operation 141 | { 142 | Console.WriteLine("C" + i++); 143 | } 144 | } 145 | 146 | public static async CoopTask Producer5() 147 | { 148 | var task = await CoopTask.Capture(); // Capture the underlying 'Task' 149 | 150 | Console.WriteLine("P 0"); 151 | 152 | await task.Yield(); // Yield control back to parent 153 | 154 | await Task.Delay(100).ConfigureAwait(false); // Use any async constructs 155 | 156 | Console.WriteLine("P 1"); 157 | 158 | await task.Yield(); // Yield control 159 | 160 | await Task.Delay(100); 161 | 162 | Console.WriteLine("P 2"); 163 | 164 | await task.Break(); // Mark the task as completed 165 | 166 | Console.WriteLine("P 3"); // Will not be run. 167 | } 168 | 169 | public static async Task Consumer5() 170 | { 171 | Console.WriteLine("C 0"); 172 | 173 | var p = Producer5(); 174 | 175 | await p.MoveNextAsync(); 176 | 177 | Console.WriteLine("C 1"); 178 | 179 | await p.MoveNextAsync(); 180 | 181 | Console.WriteLine("C 2"); 182 | 183 | await p.MoveNextAsync(); 184 | 185 | Console.WriteLine("C 3"); 186 | 187 | await p; 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/AsyncEnumeratorExamples/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("AsyncEnumeratorExamples")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("AsyncEnumeratorExamples")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("6571283f-d632-4118-98f1-926747f87193")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/AsyncEnumeratorExamples/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/AsyncEnumeratorTests/AsyncEnumeratorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using AsyncEnumerator; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | 6 | namespace AsyncEnumeratorTests 7 | { 8 | [TestClass] 9 | public class AsyncEnumeratorTests 10 | { 11 | [TestMethod] 12 | [ExpectedException(typeof(Exception), "Awaiting failed Task did not throw.")] 13 | public async Task ThrowsOnMoveNext() 14 | { 15 | var seq = ExceptionTest1(); 16 | while(await seq.MoveNextAsync()){} 17 | } 18 | 19 | [TestMethod] 20 | public async Task EnumerationAdvancesCorrectlyAndCompletes1() 21 | { 22 | var seq = Test1(); 23 | 24 | await seq.MoveNextAsync(); 25 | Assert.AreEqual(seq.Current, 1, $"First call to {nameof(seq.MoveNextAsync)} did not advance the enumeration correctly."); 26 | 27 | await seq.MoveNextAsync(); 28 | Assert.AreEqual(seq.Current, 2, $"Call to {nameof(seq.MoveNextAsync)} did not advance the enumeration correctly."); 29 | 30 | await seq.MoveNextAsync(); 31 | Assert.AreEqual(seq.Current, 3, $"Call to {nameof(seq.MoveNextAsync)} did not advance the enumeration correctly."); 32 | 33 | Assert.IsFalse(await seq.MoveNextAsync(), $"Call to {nameof(seq.MoveNextAsync)} did not return false after enumeration completed."); 34 | 35 | Assert.IsTrue(seq.IsCompleted, "Enumeration did not complete after return."); 36 | } 37 | 38 | [TestMethod] 39 | public async Task EnumerationAdvancesCorrectlyAndCompletes2() 40 | { 41 | var seq = Test2(); 42 | 43 | await seq.MoveNextAsync(); 44 | Assert.AreEqual(seq.Current, 1, $"First call to {nameof(seq.MoveNextAsync)} did not advance the enumeration correctly."); 45 | 46 | await seq.MoveNextAsync(); 47 | Assert.AreEqual(seq.Current, 2, $"Call to {nameof(seq.MoveNextAsync)} did not advance the enumeration correctly."); 48 | 49 | await seq.MoveNextAsync(); 50 | Assert.AreEqual(seq.Current, 3, $"Call to {nameof(seq.MoveNextAsync)} did not advance the enumeration correctly."); 51 | 52 | Assert.IsFalse(await seq.MoveNextAsync(), $"Call to {nameof(seq.MoveNextAsync)} did not return false after enumeration completed."); 53 | 54 | Assert.IsTrue(seq.IsCompleted, "Enumeration did not complete after return."); 55 | } 56 | 57 | 58 | private static async AsyncEnumerator ExceptionTest1() 59 | { 60 | var yield = await AsyncEnumerator.Capture(); 61 | 62 | await yield.Return(1); 63 | 64 | throw new Exception(); 65 | } 66 | 67 | private static async AsyncEnumerator Test1() 68 | { 69 | var yield = await AsyncEnumerator.Capture(); 70 | 71 | await yield.Return(1); 72 | 73 | await yield.Return(2); 74 | 75 | await yield.Return(3); 76 | 77 | return yield.Break(); 78 | } 79 | 80 | private static async AsyncEnumerator Test2() 81 | { 82 | var yield = await AsyncEnumerator.Capture(); 83 | 84 | for (var i = 1; i <= 3; i++) 85 | { 86 | await yield.Return(i); 87 | } 88 | 89 | return yield.Break(); 90 | } 91 | 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/AsyncEnumeratorTests/AsyncEnumeratorTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {57EC6540-AF1C-4A5E-884B-79A8A0FDCA96} 8 | Library 9 | Properties 10 | AsyncEnumeratorTests 11 | AsyncEnumeratorTests 12 | v4.5.2 13 | 512 14 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 15 | 15.0 16 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 17 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 18 | False 19 | UnitTest 20 | 21 | 22 | 23 | 24 | true 25 | full 26 | false 27 | bin\Debug\ 28 | DEBUG;TRACE 29 | prompt 30 | 4 31 | 32 | 33 | pdbonly 34 | true 35 | bin\Release\ 36 | TRACE 37 | prompt 38 | 4 39 | 40 | 41 | 42 | ..\packages\Microsoft.Reactive.Testing.3.1.1\lib\net45\Microsoft.Reactive.Testing.dll 43 | 44 | 45 | ..\packages\MSTest.TestFramework.1.1.11\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll 46 | 47 | 48 | ..\packages\MSTest.TestFramework.1.1.11\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll 49 | 50 | 51 | 52 | 53 | ..\packages\System.Reactive.Core.3.1.1\lib\net45\System.Reactive.Core.dll 54 | 55 | 56 | ..\packages\System.Reactive.Interfaces.3.1.1\lib\net45\System.Reactive.Interfaces.dll 57 | 58 | 59 | ..\packages\System.Reactive.Linq.3.1.1\lib\net45\System.Reactive.Linq.dll 60 | 61 | 62 | ..\packages\System.Reactive.PlatformServices.3.1.1\lib\net45\System.Reactive.PlatformServices.dll 63 | 64 | 65 | ..\packages\System.Reactive.Windows.Threading.3.1.1\lib\net45\System.Reactive.Windows.Threading.dll 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | {72bd723b-82b2-41a4-972d-e8cf56a6f239} 83 | AsyncEnumerator 84 | 85 | 86 | 87 | 88 | 89 | 90 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/AsyncEnumeratorTests/AsyncSequenceTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using AsyncEnumerator; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | 6 | namespace AsyncEnumeratorTests 7 | { 8 | [TestClass] 9 | public class AsyncSequenceTests 10 | { 11 | [TestMethod] 12 | [ExpectedException(typeof(Exception), "Awaiting failed Task did not throw.")] 13 | public async Task ThrowsOnMoveNext() 14 | { 15 | var seq = ExceptionTest1(); 16 | while(await seq.MoveNextAsync()){} 17 | } 18 | 19 | [TestMethod] 20 | public async Task EnumerationAdvancesCorrectlyAndCompletes1() 21 | { 22 | var seq = Test1(); 23 | 24 | await seq.MoveNextAsync(); 25 | Assert.AreEqual(1, seq.Current, $"First call to {nameof(seq.MoveNextAsync)} did not advance the enumeration correctly."); 26 | 27 | await seq.MoveNextAsync(); 28 | Assert.AreEqual(2, seq.Current, $"Call to {nameof(seq.MoveNextAsync)} did not advance the enumeration correctly."); 29 | 30 | await seq.MoveNextAsync(); 31 | Assert.AreEqual(3, seq.Current, $"Call to {nameof(seq.MoveNextAsync)} did not advance the enumeration correctly."); 32 | 33 | Assert.IsFalse(await seq.MoveNextAsync(), $"Call to {nameof(seq.MoveNextAsync)} did not return false after enumeration completed."); 34 | 35 | Assert.IsTrue(seq.IsCompleted, "Enumeration did not complete after return."); 36 | } 37 | 38 | [TestMethod] 39 | public async Task EnumerationAdvancesCorrectlyAndCompletes2() 40 | { 41 | var seq = Test2(); 42 | 43 | await seq.MoveNextAsync(); 44 | Assert.AreEqual(1, seq.Current, $"First call to {nameof(seq.MoveNextAsync)} did not advance the enumeration correctly."); 45 | 46 | await seq.MoveNextAsync(); 47 | Assert.AreEqual(2, seq.Current, $"Call to {nameof(seq.MoveNextAsync)} did not advance the enumeration correctly."); 48 | 49 | await seq.MoveNextAsync(); 50 | Assert.AreEqual(3, seq.Current, $"Call to {nameof(seq.MoveNextAsync)} did not advance the enumeration correctly."); 51 | 52 | Assert.IsFalse(await seq.MoveNextAsync(), $"Call to {nameof(seq.MoveNextAsync)} did not return false after enumeration completed."); 53 | 54 | Assert.IsTrue(seq.IsCompleted, "Enumeration did not complete after return."); 55 | } 56 | 57 | private static async AsyncEnumerator ExceptionTest1() 58 | { 59 | var yield = await AsyncEnumerator.Capture(); 60 | 61 | await yield.Return(1); 62 | 63 | throw new Exception(); 64 | } 65 | 66 | private static async AsyncSequence Test1() 67 | { 68 | var yield = await AsyncSequence.Capture(); 69 | 70 | await Task.Delay(0).ConfigureAwait(false); 71 | 72 | yield.Return(1); 73 | 74 | yield.Return(2); 75 | 76 | yield.Return(3); 77 | 78 | return yield.Break(); 79 | } 80 | 81 | private static async AsyncSequence Test2() 82 | { 83 | var yield = await AsyncSequence.Capture(); 84 | 85 | await Task.Delay(0).ConfigureAwait(false); 86 | 87 | for (var i = 1; i <= 3; i++) 88 | { 89 | yield.Return(i); 90 | } 91 | 92 | return yield.Break(); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/AsyncEnumeratorTests/CoopTaskTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using AsyncEnumerator; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | 6 | namespace AsyncEnumeratorTests 7 | { 8 | [TestClass] 9 | public class CoopTaskTests 10 | { 11 | [TestMethod] 12 | [ExpectedException(typeof(Exception), "Awaiting failed Task did not throw.")] 13 | public async Task ThrowsOnMoveNext() 14 | { 15 | var seq = ExceptionTest1(); 16 | while(await seq.MoveNextAsync()){} 17 | } 18 | 19 | [TestMethod] 20 | public async Task EnumerationAdvancesCorrectlyAndCompletes1() 21 | { 22 | var seq = Test1(); 23 | 24 | await seq.MoveNextAsync(); 25 | await seq.MoveNextAsync(); 26 | await seq.MoveNextAsync(); 27 | 28 | Assert.IsFalse(await seq.MoveNextAsync(), $"Call to {nameof(seq.MoveNextAsync)} did not return false after enumeration completed."); 29 | 30 | Assert.IsTrue(seq.IsCompleted, "Enumeration did not complete after return."); 31 | } 32 | 33 | [TestMethod] 34 | public async Task EnumerationAdvancesCorrectlyAndCompletes2() 35 | { 36 | var seq = Test2(); 37 | 38 | await seq.MoveNextAsync(); 39 | await seq.MoveNextAsync(); 40 | await seq.MoveNextAsync(); 41 | 42 | Assert.IsFalse(await seq.MoveNextAsync(), $"Call to {nameof(seq.MoveNextAsync)} did not return false after enumeration completed."); 43 | 44 | Assert.IsTrue(seq.IsCompleted, "Enumeration did not complete after return."); 45 | } 46 | 47 | 48 | private static async CoopTask ExceptionTest1() 49 | { 50 | var yield = await CoopTask.Capture(); 51 | 52 | await yield.Yield(); 53 | 54 | throw new Exception(); 55 | } 56 | 57 | private static async CoopTask Test1() 58 | { 59 | var yield = await CoopTask.Capture(); 60 | 61 | await yield.Yield(); 62 | 63 | await yield.Yield(); 64 | 65 | await yield.Yield(); 66 | } 67 | 68 | private static async CoopTask Test2() 69 | { 70 | var yield = await CoopTask.Capture(); 71 | 72 | for (var i = 1; i <= 3; i++) 73 | { 74 | await yield.Yield(); 75 | } 76 | } 77 | 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/AsyncEnumeratorTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: AssemblyTitle("AsyncEnumeratorTests")] 6 | [assembly: AssemblyDescription("")] 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("AsyncEnumeratorTests")] 10 | [assembly: AssemblyCopyright("Copyright © 2017")] 11 | [assembly: AssemblyTrademark("")] 12 | [assembly: AssemblyCulture("")] 13 | 14 | [assembly: ComVisible(false)] 15 | 16 | [assembly: Guid("57ec6540-af1c-4a5e-884b-79a8a0fdca96")] 17 | 18 | // [assembly: AssemblyVersion("1.0.*")] 19 | [assembly: AssemblyVersion("1.0.0.0")] 20 | [assembly: AssemblyFileVersion("1.0.0.0")] 21 | -------------------------------------------------------------------------------- /src/AsyncEnumeratorTests/TaskLikeObservableTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reactive.Linq; 4 | using AsyncEnumerator; 5 | using Microsoft.Reactive.Testing; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | 8 | namespace AsyncEnumeratorTests 9 | { 10 | [TestClass] 11 | public class TaskLikeObservableTests 12 | { 13 | [TestMethod] 14 | public void SequenceRunsCorrectly() 15 | { 16 | var sched = new TestScheduler(); 17 | var results = new List(); 18 | 19 | Test1().SubscribeOn(sched).Subscribe(i => results.Add(i)); 20 | 21 | sched.Start(); 22 | 23 | CollectionAssert.AreEqual(results, new[] {1, 2, 3}, "Sequences do not match"); 24 | } 25 | 26 | private static async TaskLikeObservable Test1() 27 | { 28 | var ob = await TaskLikeObservable.Capture(); 29 | 30 | await ob.Subscription.ConfigureAwait(false); 31 | 32 | ob.OnNext(1); 33 | 34 | ob.OnNext(2); 35 | 36 | ob.OnNext(3); 37 | 38 | return ob.OnCompleted(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/AsyncEnumeratorTests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | --------------------------------------------------------------------------------