├── .gitignore
├── banner.png
├── icon.png
├── AsyncEnumerable.pfx
├── src
├── IAsyncDisposable.cs
├── Extensions
│ ├── ForEachAsyncBreakException.cs
│ ├── ParallelForEachException.cs
│ ├── ForEachAsync.cs
│ ├── AdapterExtensions.cs
│ └── ForEachAsyncExtensions.cs
├── AsyncEnumerationCanceledException.cs
├── Internals
│ ├── CancellationTokenEx.cs
│ ├── EmptyAsyncEnumerable.cs
│ ├── EmptyAsyncEnumerator.cs
│ ├── CurrentValueContainer.cs
│ ├── TaskEx.cs
│ ├── AsyncEnumerableWrapper.cs
│ ├── EnumerableAdapter.cs
│ ├── EnumeratorAdapter.cs
│ ├── AsyncEnumeratorWrapper.cs
│ └── TaskCompletionSource.cs
├── AssemblyInfo.cs
├── IAsyncEnumerator.cs
├── AsyncEnumerable.csproj
├── IAsyncEnumerable.cs
├── AsyncEnumerable.cs
└── AsyncEnumerator.cs
├── netfx45
├── tests
│ ├── packages.config
│ ├── AssemblyInfo.cs
│ └── AsyncEnumerable.Tests.NetFx45.csproj
└── lib
│ ├── packages.config
│ └── AsyncEnumerable.NetFx45.csproj
├── Tests
├── AsyncEnumerable.Tests.csproj
├── Program.cs
├── PerformanceTests.cs
├── ForEachAsyncTests.cs
├── AsyncEnumeratorTests.cs
├── EnumeratorLinqStyleExtensionsTests.cs
└── EnumerableLinqStyleExtensionsTests.cs
├── LICENSE
├── AsyncEnumerable.Symbols.nuspec
├── AsyncEnumerable.sln
├── NUGET-DOC.md
├── AsyncEnumerable.nuspec
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | obj/
2 | bin/
3 | .vs/
4 | *.user
5 | packages/
6 |
--------------------------------------------------------------------------------
/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dasync/AsyncEnumerable/HEAD/banner.png
--------------------------------------------------------------------------------
/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dasync/AsyncEnumerable/HEAD/icon.png
--------------------------------------------------------------------------------
/AsyncEnumerable.pfx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Dasync/AsyncEnumerable/HEAD/AsyncEnumerable.pfx
--------------------------------------------------------------------------------
/src/IAsyncDisposable.cs:
--------------------------------------------------------------------------------
1 | #if !NETSTANDARD2_1 && !NETSTANDARD2_0 && !NET461
2 | using System.Threading.Tasks;
3 |
4 | namespace Dasync.Collections
5 | {
6 | public interface IAsyncDisposable
7 | {
8 | ValueTask DisposeAsync();
9 | }
10 | }
11 | #endif
12 |
--------------------------------------------------------------------------------
/src/Extensions/ForEachAsyncBreakException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Dasync.Collections
4 | {
5 | ///
6 | /// This exception is thrown when you call .
7 | ///
8 | public sealed class ForEachAsyncBreakException : OperationCanceledException { }
9 | }
10 |
--------------------------------------------------------------------------------
/src/AsyncEnumerationCanceledException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Dasync.Collections
4 | {
5 | ///
6 | /// This exception is thrown when you call
7 | /// or when the enumerator is disposed before reaching the end of enumeration.
8 | ///
9 | public sealed class AsyncEnumerationCanceledException : OperationCanceledException { }
10 | }
11 |
--------------------------------------------------------------------------------
/netfx45/tests/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/netfx45/lib/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/Internals/CancellationTokenEx.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 |
3 | namespace Dasync.Collections.Internals
4 | {
5 | internal static class CancellationTokenEx
6 | {
7 | public static readonly CancellationToken Canceled;
8 |
9 | static CancellationTokenEx()
10 | {
11 | var cts = new CancellationTokenSource();
12 | cts.Cancel();
13 | Canceled = cts.Token;
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/src/Extensions/ParallelForEachException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace Dasync.Collections
5 | {
6 | ///
7 | /// Used in ParallelForEachAsync<T> extension method
8 | ///
9 | public class ParallelForEachException : AggregateException
10 | {
11 | ///
12 | /// Constructor
13 | ///
14 | public ParallelForEachException(IEnumerable innerExceptions)
15 | : base(innerExceptions)
16 | {
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Extensions/ForEachAsync.cs:
--------------------------------------------------------------------------------
1 | namespace Dasync.Collections
2 | {
3 | ///
4 | /// Class to provide access to static method.
5 | ///
6 | public static class ForEachAsync
7 | {
8 | ///
9 | /// Stops ForEachAsync iteration (similar to 'break' statement)
10 | ///
11 | /// Always throws this exception to stop the ForEachAsync iteration
12 | public static void Break()
13 | {
14 | throw new ForEachAsyncBreakException();
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Internals/EmptyAsyncEnumerable.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading;
3 | using Dasync.Collections;
4 |
5 | namespace Dasync.Collections.Internals
6 | {
7 | internal sealed class EmptyAsyncEnumerable : IAsyncEnumerable
8 | {
9 | public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default)
10 | => AsyncEnumerator.Empty;
11 |
12 | #if !NETSTANDARD2_1 && !NETSTANDARD2_0 && !NET461
13 | IAsyncEnumerator IAsyncEnumerable.GetAsyncEnumerator(CancellationToken cancellationToken)
14 | => AsyncEnumerator.Empty;
15 | #endif
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 | using System.Runtime.InteropServices;
4 |
5 | [assembly: AssemblyVersion("4.0.2")]
6 | [assembly: AssemblyFileVersion("4.0.2")]
7 | [assembly: AssemblyTitle("AsyncEnumerable")]
8 | [assembly: AssemblyProduct("AsyncEnumerable")]
9 | [assembly: AssemblyDescription("Asynchronous enumeration with IAsyncEnumerator, ForEachAsync(), and ParallelForEachAsync()")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("D-ASYNC")]
12 | [assembly: AssemblyCopyright("Copyright © 2019")]
13 | [assembly: AssemblyTrademark("")]
14 | [assembly: AssemblyCulture("")]
15 | [assembly: CLSCompliant(true)]
16 | [assembly: ComVisible(false)]
17 | [assembly: Guid("111536c3-dabf-48cf-b993-e504c0bc5ce7")]
--------------------------------------------------------------------------------
/src/Internals/EmptyAsyncEnumerator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using Dasync.Collections;
5 |
6 | namespace Dasync.Collections.Internals
7 | {
8 | internal sealed class EmptyAsyncEnumerator : IAsyncEnumerator, IAsyncEnumerator
9 | {
10 | public T Current
11 | {
12 | get
13 | {
14 | throw new InvalidOperationException("The enumerator has reached the end of the collection");
15 | }
16 | }
17 |
18 | object IAsyncEnumerator.Current => Current;
19 |
20 | public ValueTask MoveNextAsync() => new ValueTask(false);
21 |
22 | public ValueTask DisposeAsync() => new ValueTask();
23 |
24 | public void Dispose() { }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Internals/CurrentValueContainer.cs:
--------------------------------------------------------------------------------
1 | namespace Dasync.Collections.Internals
2 | {
3 | ///
4 | /// Internal base type for and
5 | ///
6 | public abstract class CurrentValueContainer : AsyncEnumerator
7 | {
8 | private T _currentValue;
9 |
10 | internal T CurrentValue
11 | {
12 | get
13 | {
14 | return _currentValue;
15 | }
16 | set
17 | {
18 | _currentValue = value;
19 | HasCurrentValue = true;
20 | }
21 | }
22 |
23 | internal bool HasCurrentValue
24 | {
25 | get;
26 | private set;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Internals/TaskEx.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace Dasync.Collections.Internals
5 | {
6 | internal static class TaskEx
7 | {
8 | public static readonly Task True = Task.FromResult(true);
9 | public static readonly Task False = Task.FromResult(false);
10 | public static readonly Task Completed =
11 | #if NETFX4_5 || NETFX4_5_2
12 | True;
13 | #else
14 | Task.CompletedTask;
15 | #endif
16 |
17 | public static Task FromException(Exception ex)
18 | {
19 | #if NETFX4_5 || NETFX4_5_2
20 | var tcs = new TaskCompletionSource();
21 | tcs.SetException(ex);
22 | return tcs.Task;
23 | #else
24 | return Task.FromException(ex);
25 | #endif
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/Tests/AsyncEnumerable.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.0
5 | False
6 | Tests
7 | Tests
8 | exe
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 D-ASYNC
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 |
--------------------------------------------------------------------------------
/src/Internals/AsyncEnumerableWrapper.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading;
3 | using Dasync.Collections;
4 |
5 | namespace Dasync.Collections.Internals
6 | {
7 | internal sealed class AsyncEnumerableWrapper : IAsyncEnumerable, IAsyncEnumerable
8 | {
9 | private readonly IEnumerable _enumerable;
10 | private readonly bool _runSynchronously;
11 |
12 | public AsyncEnumerableWrapper(IEnumerable enumerable, bool runSynchronously)
13 | {
14 | _enumerable = enumerable;
15 | _runSynchronously = runSynchronously;
16 | }
17 |
18 | IAsyncEnumerator IAsyncEnumerable.GetAsyncEnumerator(CancellationToken cancellationToken)
19 | => new AsyncEnumeratorWrapper(_enumerable.GetEnumerator(), _runSynchronously)
20 | { MasterCancellationToken = cancellationToken };
21 |
22 | public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default)
23 | => new AsyncEnumeratorWrapper(_enumerable.GetEnumerator(), _runSynchronously)
24 | { MasterCancellationToken = cancellationToken };
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Internals/EnumerableAdapter.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using Dasync.Collections;
4 |
5 | namespace Dasync.Collections.Internals
6 | {
7 | #if !NETSTANDARD2_1 && !NETSTANDARD2_0 && !NET461
8 | internal sealed class EnumerableAdapter : IEnumerable
9 | {
10 | private readonly IAsyncEnumerable _asyncEnumerable;
11 |
12 | public EnumerableAdapter(IAsyncEnumerable asyncEnumerable)
13 | {
14 | _asyncEnumerable = asyncEnumerable;
15 | }
16 |
17 | public IEnumerator GetEnumerator() =>
18 | new EnumeratorAdapter(_asyncEnumerable.GetAsyncEnumerator());
19 | }
20 | #endif
21 |
22 | internal sealed class EnumerableAdapter : IEnumerable, IEnumerable
23 | {
24 | private readonly IAsyncEnumerable _asyncEnumerable;
25 |
26 | public EnumerableAdapter(IAsyncEnumerable asyncEnumerable)
27 | {
28 | _asyncEnumerable = asyncEnumerable;
29 | }
30 |
31 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
32 |
33 | public IEnumerator GetEnumerator() =>
34 | new EnumeratorAdapter(_asyncEnumerable.GetAsyncEnumerator());
35 | }
36 | }
--------------------------------------------------------------------------------
/AsyncEnumerable.Symbols.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | AsyncEnumerator
5 | 4.0.2
6 |
7 | AsyncEnumerator symbols
8 | AsyncEnumerator symbols
9 | AsyncEnumerator symbols
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/IAsyncEnumerator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading.Tasks;
3 |
4 | namespace Dasync.Collections
5 | {
6 | ///
7 | /// Supports a simple asynchronous iteration over a non-generic collection
8 | ///
9 | public interface IAsyncEnumerator : IDisposable, IAsyncDisposable
10 | {
11 | ///
12 | /// Gets the current element in the collection.
13 | ///
14 | object Current { get; }
15 |
16 | ///
17 | /// Advances the enumerator to the next element of the collection asynchronously
18 | ///
19 | /// Returns a Task that does transition to the next element. The result of the task is True if the enumerator was successfully advanced to the next element, or False if the enumerator has passed the end of the collection.
20 | ValueTask MoveNextAsync();
21 | }
22 |
23 | #if !NETSTANDARD2_1 && !NETSTANDARD2_0 && !NET461
24 | ///
25 | /// Supports a simple asynchronous iteration over a collection of typed items
26 | ///
27 | /// The type of items in the collection
28 | public interface IAsyncEnumerator : IAsyncEnumerator
29 | {
30 | ///
31 | /// Gets the element in the collection at the current position of the enumerator.
32 | ///
33 | new T Current { get; }
34 | }
35 | #endif
36 | }
37 |
--------------------------------------------------------------------------------
/netfx45/tests/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("Tests")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("Tests")]
13 | [assembly: AssemblyCopyright("Copyright © 2019")]
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("9557379c-26e0-448f-ab22-b197885b05e2")]
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/AsyncEnumerable.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard1.4;net461;netstandard2.0;netstandard2.1
5 | True
6 | ../AsyncEnumerable.pfx
7 | False
8 | False
9 | AsyncEnumerable
10 | Dasync.Collections
11 | false
12 | latest
13 | bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/IAsyncEnumerable.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 |
3 | namespace Dasync.Collections
4 | {
5 | ///
6 | /// Exposes an asynchronous enumerator, which supports a simple iteration over a non-generic collection
7 | ///
8 | public interface IAsyncEnumerable
9 | {
10 | ///
11 | /// Creates an enumerator that iterates through a collection asynchronously
12 | ///
13 | /// A cancellation token to cancel creation of the enumerator in case if it takes a lot of time
14 | /// Returns a task with the created enumerator as result on completion
15 | IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default);
16 | }
17 |
18 | #if !NETSTANDARD2_1 && !NETSTANDARD2_0 && !NET461
19 | ///
20 | /// Exposes the asynchronous enumerator, which supports a simple iteration over a collection of typed items
21 | ///
22 | /// The type of items in the collection
23 | public interface IAsyncEnumerable : IAsyncEnumerable
24 | {
25 | ///
26 | /// Creates an enumerator that iterates through a collection asynchronously
27 | ///
28 | /// A cancellation token to cancel creation of the enumerator in case if it takes a lot of time
29 | /// Returns a task with the created enumerator as result on completion
30 | new IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default);
31 | }
32 | #endif
33 | }
34 |
--------------------------------------------------------------------------------
/src/Internals/EnumeratorAdapter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using Dasync.Collections;
5 |
6 | namespace Dasync.Collections.Internals
7 | {
8 | #if !NETSTANDARD2_1 && !NETSTANDARD2_0 && !NET461
9 | internal sealed class EnumeratorAdapter : IEnumerator
10 | {
11 | private readonly IAsyncEnumerator _asyncEnumerator;
12 |
13 | public EnumeratorAdapter(IAsyncEnumerator asyncEnumerator)
14 | {
15 | _asyncEnumerator = asyncEnumerator;
16 | }
17 |
18 | public object Current => _asyncEnumerator.Current;
19 |
20 | public bool MoveNext() => _asyncEnumerator.MoveNextAsync().GetAwaiter().GetResult();
21 |
22 | public void Reset() => throw new NotSupportedException("The IEnumerator.Reset() method is obsolete. Create a new enumerator instead.");
23 |
24 | public void Dispose() => _asyncEnumerator.Dispose();
25 | }
26 | #endif
27 |
28 | internal sealed class EnumeratorAdapter : IEnumerator, IEnumerator
29 | {
30 | private readonly IAsyncEnumerator _asyncEnumerator;
31 |
32 | public EnumeratorAdapter(IAsyncEnumerator asyncEnumerator)
33 | {
34 | _asyncEnumerator = asyncEnumerator;
35 | }
36 |
37 | public T Current => _asyncEnumerator.Current;
38 |
39 | object IEnumerator.Current => Current;
40 |
41 | public bool MoveNext() => _asyncEnumerator.MoveNextAsync().GetAwaiter().GetResult();
42 |
43 | public void Reset() => throw new NotSupportedException("The IEnumerator.Reset() method is obsolete. Create a new enumerator instead.");
44 |
45 | public void Dispose() =>
46 | _asyncEnumerator.DisposeAsync().GetAwaiter().GetResult();
47 | }
48 | }
--------------------------------------------------------------------------------
/Tests/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Reflection;
5 | using System.Threading;
6 | using NUnit.Framework.Api;
7 | using NUnit.Framework.Interfaces;
8 | using NUnit.Framework.Internal;
9 |
10 | namespace Tests
11 | {
12 | class Program
13 | {
14 | static void Main(string[] args)
15 | {
16 | var builder = new DefaultTestAssemblyBuilder();
17 | var runner = new NUnitTestAssemblyRunner(builder);
18 | runner.Load(typeof(Program).GetTypeInfo().Assembly, settings: new Dictionary { });
19 | runner.Run(new ConsoleTestListener(), TestFilter.Empty);
20 | while (runner.IsTestRunning)
21 | Thread.Sleep(500);
22 | }
23 |
24 | class ConsoleTestListener : ITestListener
25 | {
26 | private TextWriter _output = Console.Out;
27 |
28 | public void TestStarted(ITest test)
29 | {
30 | }
31 |
32 | public void TestFinished(ITestResult result)
33 | {
34 | var message = $"{result.ResultState.Status.ToString().ToUpperInvariant()} - {result.Test.FullName}";
35 |
36 | if (result.ResultState.Status == TestStatus.Failed)
37 | message += Environment.NewLine + " " + result.Message;
38 |
39 | _output.WriteLine(message);
40 | }
41 |
42 | public void TestOutput(TestOutput output)
43 | {
44 | _output.WriteLine(output.Text);
45 | }
46 |
47 | public void SendMessage(TestMessage message)
48 | {
49 | _output.WriteLine(message.Message);
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Internals/AsyncEnumeratorWrapper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using Dasync.Collections;
6 |
7 | namespace Dasync.Collections.Internals
8 | {
9 | internal sealed class AsyncEnumeratorWrapper : IAsyncEnumerator, IAsyncEnumerator
10 | {
11 | private readonly IEnumerator _enumerator;
12 | private readonly bool _runSynchronously;
13 |
14 | public AsyncEnumeratorWrapper(IEnumerator enumerator, bool runSynchronously)
15 | {
16 | _enumerator = enumerator;
17 | _runSynchronously = runSynchronously;
18 | }
19 |
20 | internal CancellationToken MasterCancellationToken;
21 |
22 | public T Current => _enumerator.Current;
23 |
24 | object IAsyncEnumerator.Current => Current;
25 |
26 | public ValueTask MoveNextAsync()
27 | {
28 | if (_runSynchronously)
29 | {
30 | try
31 | {
32 | return new ValueTask(_enumerator.MoveNext());
33 | }
34 | catch (Exception ex)
35 | {
36 | var tcs = new TaskCompletionSource();
37 | tcs.SetException(ex);
38 | return new ValueTask(tcs.Task);
39 | }
40 | }
41 | else
42 | {
43 | return new ValueTask(Task.Run(() => _enumerator.MoveNext(), MasterCancellationToken));
44 | }
45 | }
46 |
47 | public void Dispose()
48 | {
49 | _enumerator.Dispose();
50 | }
51 |
52 | public ValueTask DisposeAsync()
53 | {
54 | Dispose();
55 | return new ValueTask();
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Tests/PerformanceTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using Dasync.Collections;
7 | using NUnit.Framework;
8 |
9 | namespace Tests
10 | {
11 | [TestFixture]
12 | public class PerformanceTests
13 | {
14 | [Test]
15 | [Ignore("Manual testing, no assertions")]
16 | public async Task MeasureEnumerationTime()
17 | {
18 | var iterations = 1000000;
19 | var enumerator = new AsyncEnumerator(async yield =>
20 | {
21 | for (int i = 0; i < iterations; i++)
22 | {
23 | await yield.ReturnAsync(1);
24 | }
25 | });
26 |
27 | var sw = Stopwatch.StartNew();
28 | int sum = 0;
29 |
30 | while (await enumerator.MoveNextAsync())
31 | sum += enumerator.Current;
32 |
33 | var time = sw.Elapsed;
34 | Console.WriteLine($"Time taken: {time}, Sum: {sum}");
35 |
36 |
37 |
38 | sw = Stopwatch.StartNew();
39 | sum = 0;
40 |
41 | foreach (var number in EnumerateNumbers())
42 | sum += number;
43 |
44 | time = sw.Elapsed;
45 | Console.WriteLine($"Time taken: {time}, Sum: {sum}");
46 |
47 |
48 |
49 | sw = Stopwatch.StartNew();
50 | sum = 0;
51 |
52 | int _lock = 0;
53 | for (int i = 0; i < iterations; i++)
54 | {
55 | Interlocked.CompareExchange(ref _lock, 1, 0);
56 | var tcs = new TaskCompletionSource();
57 | tcs.TrySetResult(1);
58 | sum += await tcs.Task;
59 | //await Task.Yield();
60 | //await Task.Yield();
61 | Interlocked.Exchange(ref _lock, 0);
62 | }
63 |
64 | time = sw.Elapsed;
65 | Console.WriteLine($"Time taken: {time}, Sum: {sum}");
66 |
67 |
68 |
69 | sw = Stopwatch.StartNew();
70 | sum = 0;
71 |
72 | var t = Task.FromResult(1);
73 | for (int i = 0; i < iterations; i++)
74 | sum += await t;
75 |
76 | time = sw.Elapsed;
77 | Console.WriteLine($"Time taken: {time}, Sum: {sum}");
78 | }
79 |
80 | public IEnumerable EnumerateNumbers()
81 | {
82 | for (int i = 0; i < 1000000; i++)
83 | yield return 1;
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/Tests/ForEachAsyncTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using Dasync.Collections;
5 | using NUnit.Framework;
6 |
7 | #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
8 |
9 | namespace Tests
10 | {
11 | [TestFixture]
12 | public class ForEachAsyncTests
13 | {
14 | [Test]
15 | public void SimpleSyncForEach()
16 | {
17 | IAsyncEnumerable enumerable = new AsyncEnumerable(
18 | async yield =>
19 | {
20 | for (int i = 0; i < 5; i++)
21 | await yield.ReturnAsync(i);
22 | });
23 |
24 | #pragma warning disable CS0612 // Type or member is obsolete
25 | int counter = 0;
26 | foreach (var number in enumerable.ToEnumerable())
27 | {
28 | Assert.AreEqual(counter, number);
29 | counter++;
30 | }
31 | #pragma warning restore CS0612 // Type or member is obsolete
32 | }
33 |
34 | [Test]
35 | public async Task SimpleAsyncForEachWithSyncBreak()
36 | {
37 | IAsyncEnumerable enumerable = new AsyncEnumerable(
38 | async yield =>
39 | {
40 | for (int i = 0; i < 5; i++)
41 | await yield.ReturnAsync(i);
42 | });
43 |
44 | int counter = 0;
45 | await enumerable.ForEachAsync(
46 | number =>
47 | {
48 | Assert.AreEqual(counter, number);
49 | counter++;
50 | if (counter == 3) ForEachAsync.Break();
51 | });
52 |
53 | Assert.AreEqual(3, counter);
54 | }
55 |
56 | [Test]
57 | public async Task SimpleAsyncForEachWithAsyncBreak()
58 | {
59 | IAsyncEnumerable enumerable = new AsyncEnumerable(
60 | async yield =>
61 | {
62 | for (int i = 0; i < 5; i++)
63 | await yield.ReturnAsync(i);
64 | });
65 |
66 | int counter = 0;
67 | await enumerable.ForEachAsync(
68 | async number =>
69 | {
70 | Assert.AreEqual(counter, number);
71 | counter++;
72 | if (counter == 2) ForEachAsync.Break();
73 | });
74 |
75 | Assert.AreEqual(2, counter);
76 | }
77 |
78 | [Test]
79 | public async Task SimpleAsyncForEach()
80 | {
81 | IAsyncEnumerable enumerable = new AsyncEnumerable(
82 | async yield =>
83 | {
84 | for (int i = 0; i < 5; i++)
85 | await yield.ReturnAsync(i);
86 | });
87 |
88 | int counter = 0;
89 | await enumerable.ForEachAsync(
90 | number =>
91 | {
92 | Assert.AreEqual(counter, number);
93 | counter++;
94 | });
95 |
96 | Assert.AreEqual(5, counter);
97 | }
98 |
99 | [Test]
100 | public async Task RethrowProducerException()
101 | {
102 | IAsyncEnumerable enumerable = new AsyncEnumerable(
103 | async yield =>
104 | {
105 | throw new ArgumentException("test");
106 | });
107 |
108 | try
109 | {
110 | await enumerable.ForEachAsync(
111 | number =>
112 | {
113 | Assert.Fail("must never be called due to the exception");
114 | });
115 | }
116 | catch (ArgumentException)
117 | {
118 | Assert.Pass();
119 | }
120 |
121 | Assert.Fail("Expected an exception to be thrown");
122 | }
123 |
124 | [Test]
125 | public async Task RethrowConsumerException()
126 | {
127 | IAsyncEnumerable enumerable = new AsyncEnumerable(
128 | async yield =>
129 | {
130 | await yield.ReturnAsync(123);
131 | });
132 |
133 | try
134 | {
135 | await enumerable.ForEachAsync(
136 | number =>
137 | {
138 | throw new ArgumentException("test");
139 | });
140 | }
141 | catch (ArgumentException)
142 | {
143 | Assert.Pass();
144 | }
145 |
146 | Assert.Fail("Expected an exception to be thrown");
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/netfx45/tests/AsyncEnumerable.Tests.NetFx45.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Debug
7 | AnyCPU
8 | {9557379C-26E0-448F-AB22-B197885B05E2}
9 | Library
10 | Properties
11 | Tests
12 | Tests
13 | v4.5
14 | 512
15 |
16 |
17 |
18 |
19 | true
20 | full
21 | false
22 | bin\Debug\
23 | DEBUG;TRACE
24 | prompt
25 | 4
26 |
27 |
28 | pdbonly
29 | true
30 | bin\Release\
31 | TRACE
32 | prompt
33 | 4
34 |
35 |
36 |
37 | ..\..\packages\NUnit.3.11.0\lib\net45\nunit.framework.dll
38 |
39 |
40 |
41 | ..\..\packages\System.Runtime.CompilerServices.Unsafe.4.5.0\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll
42 |
43 |
44 | ..\..\packages\System.Threading.Tasks.Extensions.4.5.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll
45 |
46 |
47 | ..\..\packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll
48 |
49 |
50 |
51 |
52 | EnumerableLinqStyleExtensionsTests.cs
53 |
54 |
55 | EnumeratorLinqStyleExtensionsTests.cs
56 |
57 |
58 |
59 | AsyncEnumeratorTests.cs
60 |
61 |
62 | ForEachAsyncTests.cs
63 |
64 |
65 | PerformanceTests.cs
66 |
67 |
68 |
69 |
70 | {111536c3-dabf-48cf-b993-e504c0bc5ce7}
71 | AsyncEnumerable.NetFx45
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | 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}.
84 |
85 |
86 |
87 |
94 |
--------------------------------------------------------------------------------
/src/AsyncEnumerable.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using Dasync.Collections.Internals;
6 |
7 | namespace Dasync.Collections
8 | {
9 | ///
10 | /// Base abstract class that implements .
11 | /// Use concrete implementation or .
12 | ///
13 | public abstract class AsyncEnumerable : IAsyncEnumerable
14 | {
15 | ///
16 | /// Returns pre-cached empty collection
17 | ///
18 | public static IAsyncEnumerable Empty() => AsyncEnumerable.Empty;
19 |
20 | IAsyncEnumerator IAsyncEnumerable.GetAsyncEnumerator(CancellationToken cancellationToken)
21 | {
22 | throw new NotImplementedException();
23 | }
24 | }
25 |
26 | ///
27 | /// Helps to enumerate items in a collection asynchronously
28 | ///
29 | ///
30 | ///
31 | /// IAsyncEnumerable<int> ProduceNumbers(int start, int end)
32 | /// {
33 | /// return new AsyncEnumerable<int>(async yield => {
34 | /// for (int number = start; number <= end; number++)
35 | /// await yield.ReturnAsync(number);
36 | /// });
37 | /// }
38 | ///
39 | /// async Task ConsumeAsync()
40 | /// {
41 | /// var asyncEnumerableCollection = ProduceNumbers(start: 1, end: 10);
42 | /// await asyncEnumerableCollection.ForEachAsync(async number => {
43 | /// await Console.Out.WriteLineAsync(number);
44 | /// });
45 | /// }
46 | ///
47 | ///
48 | public class AsyncEnumerable : AsyncEnumerable, IAsyncEnumerable, IAsyncEnumerable
49 | {
50 | private readonly Func.Yield, Task> _enumerationFunction;
51 |
52 | ///
53 | /// A pre-cached empty collection
54 | ///
55 | public readonly static IAsyncEnumerable Empty = new EmptyAsyncEnumerable();
56 |
57 | ///
58 | /// Constructor
59 | ///
60 | /// A function that enumerates items in a collection asynchronously
61 | public AsyncEnumerable(Func.Yield, Task> enumerationFunction)
62 | {
63 | _enumerationFunction = enumerationFunction;
64 | }
65 |
66 | ///
67 | /// Creates an enumerator that iterates through a collection asynchronously
68 | ///
69 | /// A cancellation token to cancel creation of the enumerator in case if it takes a lot of time
70 | /// Returns a task with the created enumerator as result on completion
71 | public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default)
72 | => new AsyncEnumerator(_enumerationFunction) { MasterCancellationToken = cancellationToken };
73 |
74 | IAsyncEnumerator IAsyncEnumerable.GetAsyncEnumerator(CancellationToken cancellationToken)
75 | => new AsyncEnumerator(_enumerationFunction) { MasterCancellationToken = cancellationToken };
76 | }
77 |
78 | ///
79 | /// Similar to , but allows you to pass a state object into the enumeration function, what can be
80 | /// used for performance optimization, so don't have to create a delegate on the fly every single time you create the enumerator.
81 | ///
82 | /// Type of items returned by
83 | /// Type of the state object
84 | public class AsyncEnumerableWithState : AsyncEnumerable, IAsyncEnumerable, IAsyncEnumerable
85 | {
86 | private readonly Func.Yield, TState, Task> _enumerationFunction;
87 |
88 | ///
89 | /// Constructor
90 | ///
91 | /// A function that enumerates items in a collection asynchronously
92 | /// A state object that is passed to the
93 | public AsyncEnumerableWithState(Func.Yield, TState, Task> enumerationFunction, TState state)
94 | {
95 | _enumerationFunction = enumerationFunction;
96 | State = state;
97 | }
98 |
99 | ///
100 | /// A user state that gets passed into the enumeration function.
101 | ///
102 | protected TState State { get; }
103 |
104 | ///
105 | /// Creates an enumerator that iterates through a collection asynchronously
106 | ///
107 | /// Returns a task with the created enumerator as result on completion
108 | public virtual IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default)
109 | => new AsyncEnumeratorWithState(_enumerationFunction, State)
110 | { MasterCancellationToken = cancellationToken };
111 |
112 | ///
113 | /// Creates an enumerator that iterates through a collection asynchronously
114 | ///
115 | /// Returns a task with the created enumerator as result on completion
116 | IAsyncEnumerator IAsyncEnumerable.GetAsyncEnumerator(CancellationToken cancellationToken)
117 | => new AsyncEnumeratorWithState(_enumerationFunction, State)
118 | { MasterCancellationToken = cancellationToken };
119 | }
120 | }
--------------------------------------------------------------------------------
/AsyncEnumerable.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.28606.126
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsyncEnumerable.NetFx45", "netfx45\lib\AsyncEnumerable.NetFx45.csproj", "{111536C3-DABF-48CF-B993-E504C0BC5CE7}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".NET Framework 4.5", ".NET Framework 4.5", "{6D19B0B6-35B3-4A6B-B060-60DD1E7F8CAB}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsyncEnumerable.Tests.NetFx45", "netfx45\tests\AsyncEnumerable.Tests.NetFx45.csproj", "{9557379C-26E0-448F-AB22-B197885B05E2}"
11 | EndProject
12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".NET Standard", ".NET Standard", "{FEFF0146-6799-40F0-A9CF-AB5AAB6584C9}"
13 | EndProject
14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsyncEnumerable", "src\AsyncEnumerable.csproj", "{2299336C-BC95-41DB-8697-5127E3754370}"
15 | EndProject
16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{6E8E24A4-11BF-46D5-B060-E993E070ED65}"
17 | ProjectSection(SolutionItems) = preProject
18 | AsyncEnumerable.nuspec = AsyncEnumerable.nuspec
19 | LICENSE = LICENSE
20 | README.md = README.md
21 | EndProjectSection
22 | EndProject
23 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AsyncEnumerable.Tests", "Tests\AsyncEnumerable.Tests.csproj", "{FFD079F6-2A1E-4D81-BE07-005DD274D336}"
24 | EndProject
25 | Global
26 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
27 | Debug|Any CPU = Debug|Any CPU
28 | Debug|x64 = Debug|x64
29 | Debug|x86 = Debug|x86
30 | Release|Any CPU = Release|Any CPU
31 | Release|x64 = Release|x64
32 | Release|x86 = Release|x86
33 | EndGlobalSection
34 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
35 | {111536C3-DABF-48CF-B993-E504C0BC5CE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
36 | {111536C3-DABF-48CF-B993-E504C0BC5CE7}.Debug|Any CPU.Build.0 = Debug|Any CPU
37 | {111536C3-DABF-48CF-B993-E504C0BC5CE7}.Debug|x64.ActiveCfg = Debug|Any CPU
38 | {111536C3-DABF-48CF-B993-E504C0BC5CE7}.Debug|x64.Build.0 = Debug|Any CPU
39 | {111536C3-DABF-48CF-B993-E504C0BC5CE7}.Debug|x86.ActiveCfg = Debug|Any CPU
40 | {111536C3-DABF-48CF-B993-E504C0BC5CE7}.Debug|x86.Build.0 = Debug|Any CPU
41 | {111536C3-DABF-48CF-B993-E504C0BC5CE7}.Release|Any CPU.ActiveCfg = Release|Any CPU
42 | {111536C3-DABF-48CF-B993-E504C0BC5CE7}.Release|Any CPU.Build.0 = Release|Any CPU
43 | {111536C3-DABF-48CF-B993-E504C0BC5CE7}.Release|x64.ActiveCfg = Release|Any CPU
44 | {111536C3-DABF-48CF-B993-E504C0BC5CE7}.Release|x64.Build.0 = Release|Any CPU
45 | {111536C3-DABF-48CF-B993-E504C0BC5CE7}.Release|x86.ActiveCfg = Release|Any CPU
46 | {111536C3-DABF-48CF-B993-E504C0BC5CE7}.Release|x86.Build.0 = Release|Any CPU
47 | {9557379C-26E0-448F-AB22-B197885B05E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
48 | {9557379C-26E0-448F-AB22-B197885B05E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
49 | {9557379C-26E0-448F-AB22-B197885B05E2}.Debug|x64.ActiveCfg = Debug|Any CPU
50 | {9557379C-26E0-448F-AB22-B197885B05E2}.Debug|x64.Build.0 = Debug|Any CPU
51 | {9557379C-26E0-448F-AB22-B197885B05E2}.Debug|x86.ActiveCfg = Debug|Any CPU
52 | {9557379C-26E0-448F-AB22-B197885B05E2}.Debug|x86.Build.0 = Debug|Any CPU
53 | {9557379C-26E0-448F-AB22-B197885B05E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
54 | {9557379C-26E0-448F-AB22-B197885B05E2}.Release|Any CPU.Build.0 = Release|Any CPU
55 | {9557379C-26E0-448F-AB22-B197885B05E2}.Release|x64.ActiveCfg = Release|Any CPU
56 | {9557379C-26E0-448F-AB22-B197885B05E2}.Release|x64.Build.0 = Release|Any CPU
57 | {9557379C-26E0-448F-AB22-B197885B05E2}.Release|x86.ActiveCfg = Release|Any CPU
58 | {9557379C-26E0-448F-AB22-B197885B05E2}.Release|x86.Build.0 = Release|Any CPU
59 | {2299336C-BC95-41DB-8697-5127E3754370}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
60 | {2299336C-BC95-41DB-8697-5127E3754370}.Debug|Any CPU.Build.0 = Debug|Any CPU
61 | {2299336C-BC95-41DB-8697-5127E3754370}.Debug|x64.ActiveCfg = Debug|Any CPU
62 | {2299336C-BC95-41DB-8697-5127E3754370}.Debug|x64.Build.0 = Debug|Any CPU
63 | {2299336C-BC95-41DB-8697-5127E3754370}.Debug|x86.ActiveCfg = Debug|Any CPU
64 | {2299336C-BC95-41DB-8697-5127E3754370}.Debug|x86.Build.0 = Debug|Any CPU
65 | {2299336C-BC95-41DB-8697-5127E3754370}.Release|Any CPU.ActiveCfg = Release|Any CPU
66 | {2299336C-BC95-41DB-8697-5127E3754370}.Release|Any CPU.Build.0 = Release|Any CPU
67 | {2299336C-BC95-41DB-8697-5127E3754370}.Release|x64.ActiveCfg = Release|Any CPU
68 | {2299336C-BC95-41DB-8697-5127E3754370}.Release|x64.Build.0 = Release|Any CPU
69 | {2299336C-BC95-41DB-8697-5127E3754370}.Release|x86.ActiveCfg = Release|Any CPU
70 | {2299336C-BC95-41DB-8697-5127E3754370}.Release|x86.Build.0 = Release|Any CPU
71 | {FFD079F6-2A1E-4D81-BE07-005DD274D336}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
72 | {FFD079F6-2A1E-4D81-BE07-005DD274D336}.Debug|Any CPU.Build.0 = Debug|Any CPU
73 | {FFD079F6-2A1E-4D81-BE07-005DD274D336}.Debug|x64.ActiveCfg = Debug|Any CPU
74 | {FFD079F6-2A1E-4D81-BE07-005DD274D336}.Debug|x64.Build.0 = Debug|Any CPU
75 | {FFD079F6-2A1E-4D81-BE07-005DD274D336}.Debug|x86.ActiveCfg = Debug|Any CPU
76 | {FFD079F6-2A1E-4D81-BE07-005DD274D336}.Debug|x86.Build.0 = Debug|Any CPU
77 | {FFD079F6-2A1E-4D81-BE07-005DD274D336}.Release|Any CPU.ActiveCfg = Release|Any CPU
78 | {FFD079F6-2A1E-4D81-BE07-005DD274D336}.Release|Any CPU.Build.0 = Release|Any CPU
79 | {FFD079F6-2A1E-4D81-BE07-005DD274D336}.Release|x64.ActiveCfg = Release|Any CPU
80 | {FFD079F6-2A1E-4D81-BE07-005DD274D336}.Release|x64.Build.0 = Release|Any CPU
81 | {FFD079F6-2A1E-4D81-BE07-005DD274D336}.Release|x86.ActiveCfg = Release|Any CPU
82 | {FFD079F6-2A1E-4D81-BE07-005DD274D336}.Release|x86.Build.0 = Release|Any CPU
83 | EndGlobalSection
84 | GlobalSection(SolutionProperties) = preSolution
85 | HideSolutionNode = FALSE
86 | EndGlobalSection
87 | GlobalSection(NestedProjects) = preSolution
88 | {111536C3-DABF-48CF-B993-E504C0BC5CE7} = {6D19B0B6-35B3-4A6B-B060-60DD1E7F8CAB}
89 | {9557379C-26E0-448F-AB22-B197885B05E2} = {6D19B0B6-35B3-4A6B-B060-60DD1E7F8CAB}
90 | {2299336C-BC95-41DB-8697-5127E3754370} = {FEFF0146-6799-40F0-A9CF-AB5AAB6584C9}
91 | {FFD079F6-2A1E-4D81-BE07-005DD274D336} = {FEFF0146-6799-40F0-A9CF-AB5AAB6584C9}
92 | EndGlobalSection
93 | GlobalSection(ExtensibilityGlobals) = postSolution
94 | SolutionGuid = {98CBAF14-E09D-4B95-ABA5-0028ADD420B1}
95 | EndGlobalSection
96 | EndGlobal
97 |
--------------------------------------------------------------------------------
/NUGET-DOC.md:
--------------------------------------------------------------------------------
1 | __1: How to use this library?__
2 |
3 | See examples above. You can the core code and lots of useful extension methods in the `Dasync.Collections` namespace.
4 |
5 |
6 | __2: Using CancellationToken__
7 |
8 | ```csharp
9 | using Dasync.Collections;
10 |
11 | IAsyncEnumerable ProduceNumbers()
12 | {
13 | return new AsyncEnumerable(async yield => {
14 |
15 | await FooAsync(yield.CancellationToken);
16 | });
17 | }
18 | ```
19 |
20 | __3: Always remember about ConfigureAwait(false)__
21 |
22 | To avoid performance degradation and possible dead-locks in ASP.NET or WPF applications (or any `SynchronizationContext`-dependent environment), you should always put `ConfigureAwait(false)` in your `await` statements:
23 |
24 | ```csharp
25 | using Dasync.Collections;
26 |
27 | IAsyncEnumerable GetValues()
28 | {
29 | return new AsyncEnumerable(async yield =>
30 | {
31 | await FooAsync().ConfigureAwait(false);
32 |
33 | // Yes, it's even needed for 'yield.ReturnAsync'
34 | await yield.ReturnAsync(123).ConfigureAwait(false);
35 | });
36 | }
37 | ```
38 |
39 | __4: Clean-up on incomplete enumeration__
40 |
41 | Imagine such situation:
42 |
43 | ```csharp
44 | using Dasync.Collections;
45 |
46 | IAsyncEnumerable ReadValuesFromQueue()
47 | {
48 | return new AsyncEnumerable(async yield =>
49 | {
50 | using (var queueClient = CreateQueueClient())
51 | {
52 | while (true)
53 | {
54 | var message = queueClient.DequeueMessageAsync();
55 | if (message == null)
56 | break;
57 |
58 | await yield.ReturnAsync(message.Value);
59 | }
60 | }
61 | });
62 | }
63 |
64 | Task ReadFirstValueOrDefaultAsync()
65 | {
66 | return ReadValuesFromQueue().FirstOrDefaultAsync();
67 | }
68 | ```
69 |
70 | The `FirstOrDefaultAsync` method will try to read first value from the `IAsyncEnumerator`, and then will just dispose it. However, disposing `AsyncEnumerator` does not mean that the `queueClient` in the lambda function will be disposed automatically as well, because async methods are just state machines which need somehow to go to a particular state to do the clean-up.
71 | To provide such behavior, when you dispose an `AsyncEnumerator` before you reach the end of enumeration, it will tell to resume your async lambda function (at `await yield.ReturnAsync()`) with the `AsyncEnumerationCanceledException` (derives from `OperationCanceledException`). Having such exception in your lambda method will break normal flow of enumeration and will go to terminal state of the underlying state machine, what will do the clean-up, i.e. dispose the `queueClient` in this case. You don't need (and shouldn't) catch that exception type, because it's handled internally by `AsyncEnumerator`. The same exception is thrown when you call `yield.Break()`.
72 |
73 | There is another option to do the cleanup on `Dispose`:
74 |
75 | ```csharp
76 | using Dasync.Collections;
77 |
78 | IAsyncEnumerator GetQueueEnumerator()
79 | {
80 | var queueClient = CreateQueueClient();
81 |
82 | return new AsyncEnumerable(async yield =>
83 | {
84 | while (true)
85 | {
86 | var message = queueClient.DequeueMessageAsync();
87 | if (message == null)
88 | break;
89 |
90 | await yield.ReturnAsync(message.Value);
91 | }
92 | },
93 | onDispose: () => queueClient.Dispose());
94 | }
95 | ```
96 |
97 | __5: Why is GetAsyncEnumeratorAsync async?__
98 |
99 | The `IAsyncEnumerable.GetAsyncEnumeratorAsync()` method is async and returns a `Task`, where the current implementation of `AsyncEnumerable` always runs that method synchronously and just returns an instance of `AsyncEnumerator`. Having interfaces allows you to do your own implementation, where classes mentioned above are just helpers. The initial idea was to be able to support database-like scenarios, where `GetAsyncEnumeratorAsync()` executes a query first (what internally returns a pointer), and the `MoveNextAsync()` enumerates through rows (by using that pointer).
100 |
101 | __6: Returning IAsyncEnumerable vs IAsyncEnumerator__
102 |
103 | When you implement a method that returns an async-enumerable collection you have a choice to return either `IAsyncEnumerable` or `IAsyncEnumerator` - the constructors of the helper classes `AsyncEnumerable` and `AsyncEnumerator` are absolutely identical. Both interfaces have same set of useful extension methods, like `ForEachAsync`.
104 |
105 | When you create an 'enumerable', you create a factory that produces 'enumerators', i.e. you can enumerate through a collection many times. On the other hand, creating an 'enumerator' is needed when you can through a collection only once.
106 |
107 | __7: Where is Reset or ResetAsync?__
108 |
109 | The `Reset` method must not be on the `IEnumerator` interface, and should be considered as deprecated. Create a new enumerator instead. This is the reason why the 'oneTimeUse' flag was removed in version 2 of this library.
110 |
111 | __8: How can I do synchronous for-each enumeration through IAsyncEnumerable?__
112 |
113 | You can use extension methods like `IAsyncEnumerable.ToEnumerable()` to use built-in `foreach` enumeration, BUT you should never do that! The general idea of this library is to avoid thread-blocking calls on worker threads, where converting an `IAsyncEnumerable` to `IEnumerable` will just defeat the whole purpose of this library. This is the reason why such synchronous extension methods are marked with `[Obsolete]` attribute.
114 |
115 | __9: What's the difference between ForEachAsync and ParallelForEachAsync?__
116 |
117 | The `ForEachAsync` allows you to go through a collection and perform an action on every single item in sequential manner. On the other hand, `ParallelForEachAsync` allows you to run the action on multiple items at the same time where the sequential
118 | order of completion is not guaranteed. For the latter, the degree of the parallelism is controlled by the `maxDegreeOfParalellism`
119 | argument, however it does not guarantee to spin up the exact amount of threads, because it depends on the [thread pool size](https://msdn.microsoft.com/en-us/library/system.threading.threadpool.setmaxthreads(v=vs.110).aspx) and its occupancy at a moment of time. Such parallel approach is much better than trying to create a task for an action for every single item on the collection and then awaiting on all of them with `Task.WhenAll`, because it adds less overhead to the runtime, better with memory usage, and helps with throttling-sensitive scenarios.
--------------------------------------------------------------------------------
/AsyncEnumerable.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | AsyncEnumerator
5 | 4.0.2
6 | sergiis,dasync
7 | MIT
8 | https://github.com/Dasync/AsyncEnumerable
9 |
10 | false
11 | https://raw.githubusercontent.com/Dasync/AsyncEnumerable/master/icon.png
12 | C# Async Streams features
13 | Introduces IAsyncEnumerable, IAsyncEnumerator, ForEachAsync(), and ParallelForEachAsync()
14 | GitHub: https://github.com/Dasync/AsyncEnumerable
15 |
16 | PROBLEM SPACE
17 |
18 | Helps to (a) create an element provider, where producing an element can take a lot of time
19 | due to dependency on other asynchronous events (e.g. wait handles, network streams), and
20 | (b) a consumer that processes those element as soon as they are ready without blocking
21 | the thread (the processing is scheduled on a worker thread instead).
22 |
23 |
24 | EXAMPLE
25 |
26 | using Dasync.Collections;
27 |
28 | static IAsyncEnumerable<int> ProduceAsyncNumbers(int start, int end)
29 | {
30 | return new AsyncEnumerable<int>(async yield => {
31 |
32 | // Just to show that ReturnAsync can be used multiple times
33 | await yield.ReturnAsync(start);
34 |
35 | for (int number = start + 1; number <= end; number++)
36 | await yield.ReturnAsync(number);
37 |
38 | // You can break the enumeration loop with the following call:
39 | yield.Break();
40 |
41 | // This won't be executed due to the loop break above
42 | await yield.ReturnAsync(12345);
43 | });
44 | }
45 |
46 | // Just to compare with synchronous version of enumerator
47 | static IEnumerable<int> ProduceNumbers(int start, int end)
48 | {
49 | yield return start;
50 |
51 | for (int number = start + 1; number <= end; number++)
52 | yield return number;
53 |
54 | yield break;
55 |
56 | yield return 12345;
57 | }
58 |
59 | static async Task ConsumeNumbersAsync()
60 | {
61 | var asyncEnumerableCollection = ProduceAsyncNumbers(start: 1, end: 10);
62 | await asyncEnumerableCollection.ForEachAsync(async number => {
63 | await Console.Out.WriteLineAsync($"{number}");
64 | });
65 | }
66 |
67 | // Just to compare with synchronous version of enumeration
68 | static void ConsumeNumbers()
69 | {
70 | var enumerableCollection = ProduceNumbers(start: 1, end: 10);
71 | foreach (var number in enumerableCollection) {
72 | Console.Out.WriteLine($"{number}");
73 | }
74 | }
75 | Introduces IAsyncEnumerable, IAsyncEnumerator, ForEachAsync(), and ParallelForEachAsync()
76 |
77 | 4.0.0: Use interfaces from Microsoft.Bcl.AsyncInterfaces package in NET Standard 2.0.
78 | 3.1.0: Add support for NET Standard 2.1, consolidate interface with Microsoft's implementation.
79 | 2.2.0: New extension methods: SelectMany, Append, Prepend, OfType, Concat, Distinct, ToDictionaryAsync, ToLookupAsync, AggregateAsync.
80 | 2.1.0: New extension methods: Batch, UnionAll, Single, SingleOrDefault, DefaultIfEmpty, Cast.
81 | 2.0.0: Revise design of the library: same features, but slight paradigm shift and interface breaking changes.
82 | 1.5.0: Add support for .NET Standard, minor improvements.
83 | 1.4.2: Add finalizer to AsyncEnumerator and call Dispose in ForEachAsync and ParallelForEachAsync extension methods.
84 | 1.4.0: Add new generic type AsyncEnumeratorWithState for performance optimization.
85 | Now IAsyncEnumerator<T> is covariant.
86 | Add ForEachAsync, ParallelForeachAsync, and LINQ-style extension methods for IAsyncEnumerator.
87 | 1.2.1: New Linq-style extension methods in System.Collections.Async namespace.
88 | 1.1.0: Add ParallelForEachAsync extension methods for IEnumerable<T> and IAsyncEnumerable<T> in System.Collections.Async namespace.
89 |
90 | IAsyncEnumerable IAsyncEnumerator ForEachAsync ParallelForEachAsync async await foreach parallel async-streams linq charp .net
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/src/Internals/TaskCompletionSource.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq.Expressions;
3 | using System.Reflection;
4 | using System.Runtime.CompilerServices;
5 | using System.Threading.Tasks;
6 |
7 | namespace Dasync.Collections.Internals
8 | {
9 | ///
10 | /// Utility methods for
11 | ///
12 | public static class TaskCompletionSource
13 | {
14 | private static Func _resetTaskFunc;
15 |
16 | static TaskCompletionSource()
17 | {
18 | // Collect all necessary fields of a Task that needs to be reset.
19 | #if NETSTANDARD
20 | var m_stateFlags = typeof(Task).GetTypeInfo().GetDeclaredField("m_stateFlags");
21 | var m_continuationObject = typeof(Task).GetTypeInfo().GetDeclaredField("m_continuationObject");
22 | var m_taskId = typeof(Task).GetTypeInfo().GetDeclaredField("m_taskId");
23 | var m_stateObject = typeof(Task).GetTypeInfo().GetDeclaredField("m_stateObject");
24 | #else
25 | var m_stateFlags = typeof(Task).GetField("m_stateFlags", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
26 | var m_continuationObject = typeof(Task).GetField("m_continuationObject", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
27 | var m_taskId = typeof(Task).GetField("m_taskId", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
28 | var m_stateObject = typeof(Task).GetField("m_stateObject", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
29 | #endif
30 |
31 | // Make sure that all of them available (has been checked with .NET Framework 4.5 only).
32 | if (m_stateFlags != null && m_continuationObject != null && m_taskId != null && m_stateObject != null)
33 | try
34 | {
35 | /* Using Linq Expressions compile a simple function:
36 | *
37 | * int ResetTask(Task task, object state)
38 | * {
39 | * task.m_stateFlags = ;
40 | * task.m_continuationObject = null;
41 | * task.m_taskId = 0;
42 | * m_stateObject = state;
43 | * return 0;
44 | * }
45 | */
46 |
47 | var defaultStateFlags = (int)m_stateFlags.GetValue(new TaskCompletionSource().Task);
48 |
49 | var targetArg = Expression.Parameter(typeof(Task), "task");
50 | var stateObjectArg = Expression.Parameter(typeof(object), "stateObject");
51 |
52 | var body = Expression.Block(
53 | Expression.Assign(Expression.MakeMemberAccess(targetArg, m_stateFlags), Expression.Constant(defaultStateFlags, typeof(int))),
54 | Expression.Assign(Expression.MakeMemberAccess(targetArg, m_continuationObject), Expression.Constant(null, typeof(object))),
55 | Expression.Assign(Expression.MakeMemberAccess(targetArg, m_taskId), Expression.Constant(0, typeof(int))),
56 | Expression.Assign(Expression.MakeMemberAccess(targetArg, m_stateObject), stateObjectArg),
57 | Expression.Constant(0, typeof(int)) // this can be anything of any type - lambda expression allows to compile Func<> only, but not an Action<>
58 | );
59 |
60 | var lambda = Expression.Lambda(body, targetArg, stateObjectArg);
61 | _resetTaskFunc = (Func)lambda.Compile();
62 |
63 | // Do initial testing of the reset function
64 | TestResetFunction();
65 | }
66 | catch
67 | {
68 | // If something goes wrong, the feature just won't be enabled.
69 | _resetTaskFunc = null;
70 | }
71 | }
72 |
73 | private static void TestResetFunction()
74 | {
75 | var stateObject1 = new object();
76 | var stateObject2 = new object();
77 | var tcs = new TaskCompletionSource();
78 |
79 | // Test reset before SetResult
80 | _resetTaskFunc(tcs.Task, stateObject1);
81 | if (tcs.Task.IsCanceled || tcs.Task.IsCompleted || tcs.Task.IsFaulted || tcs.Task.AsyncState != stateObject1)
82 | {
83 | _resetTaskFunc = null;
84 | return;
85 | }
86 |
87 | // Test SetResult
88 | tcs.SetResult(123);
89 | if (tcs.Task.IsCanceled || !tcs.Task.IsCompleted || tcs.Task.IsFaulted)
90 | {
91 | _resetTaskFunc = null;
92 | return;
93 | }
94 |
95 | // Test reset before SetCanceled
96 | _resetTaskFunc(tcs.Task, stateObject2);
97 | if (tcs.Task.IsCanceled || tcs.Task.IsCompleted || tcs.Task.IsFaulted || tcs.Task.AsyncState != stateObject2)
98 | {
99 | _resetTaskFunc = null;
100 | return;
101 | }
102 |
103 | // Test SetCanceled
104 | tcs.SetCanceled();
105 | if (!tcs.Task.IsCanceled || !tcs.Task.IsCompleted || tcs.Task.IsFaulted)
106 | {
107 | _resetTaskFunc = null;
108 | return;
109 | }
110 |
111 | // Test reset before SetException
112 | _resetTaskFunc(tcs.Task, stateObject1);
113 | if (tcs.Task.IsCanceled || tcs.Task.IsCompleted || tcs.Task.IsFaulted || tcs.Task.AsyncState != stateObject1)
114 | {
115 | _resetTaskFunc = null;
116 | return;
117 | }
118 |
119 | // Test SetException
120 | var ex = new Exception();
121 | tcs.SetException(ex);
122 | if (tcs.Task.IsCanceled || !tcs.Task.IsCompleted || !tcs.Task.IsFaulted || tcs.Task.Exception.InnerException != ex)
123 | {
124 | _resetTaskFunc = null;
125 | return;
126 | }
127 | }
128 |
129 | ///
130 | /// Forcibly disables re-use of instances in the method.
131 | /// This is just a safety switch in case when something goes wrong with re-using instances of .
132 | ///
133 | public static void DisableTaskCompletionSourceReUse()
134 | {
135 | _resetTaskFunc = null;
136 | }
137 |
138 | ///
139 | /// Resets a to initial incomplete state.
140 | /// This method by default re-uses the same instance of the by re-setting internal state of its using reflection.
141 | /// If such feature is not available or explicitly disable with the method, it just returns a new instance of a .
142 | ///
143 | /// Type of the result value
144 | /// Target to be reset or recreated. It's safe to pass null.
145 | /// Optional state object that you pass into constructor.
146 | [MethodImpl(MethodImplOptions.AggressiveInlining)]
147 | public static void Reset(ref TaskCompletionSource taskCompletionSource, object stateObject = null)
148 | {
149 | if (_resetTaskFunc != null && taskCompletionSource != null)
150 | {
151 | _resetTaskFunc(taskCompletionSource.Task, stateObject);
152 | }
153 | else
154 | {
155 | taskCompletionSource = new TaskCompletionSource();
156 | }
157 | }
158 | }
159 | }
--------------------------------------------------------------------------------
/Tests/AsyncEnumeratorTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using Dasync.Collections;
6 | using NUnit.Framework;
7 |
8 | namespace Tests
9 | {
10 | [TestFixture]
11 | public class AsyncEnumeratorTests
12 | {
13 | [Test]
14 | public async Task RaceConditionOnEndOfEnumeration()
15 | {
16 | var enumerator = new AsyncEnumerator(async yield =>
17 | {
18 | await Task.Run(async () =>
19 | {
20 | await yield.ReturnAsync(1);
21 | });
22 | });
23 |
24 | var moveResult1 = await enumerator.MoveNextAsync();
25 | var moveResult2 = await enumerator.MoveNextAsync();
26 | var moveResult3 = await enumerator.MoveNextAsync();
27 |
28 | Assert.IsTrue(moveResult1);
29 | Assert.IsFalse(moveResult2);
30 | Assert.IsFalse(moveResult3);
31 | }
32 |
33 | [Test]
34 | public void CancelEnumeration()
35 | {
36 | var cts = new CancellationTokenSource();
37 | cts.Cancel();
38 |
39 | var enumerable = new AsyncEnumerable(async yield =>
40 | {
41 | await Task.Yield();
42 | yield.CancellationToken.ThrowIfCancellationRequested();
43 | });
44 |
45 | Assert.ThrowsAsync(() => enumerable.ToListAsync(cts.Token));
46 | }
47 |
48 | [Test]
49 | public async Task DisposeAfterPartialEnumeration()
50 | {
51 | // ARRANGE
52 |
53 | var testDisposable = new TestDisposable();
54 | var enumerator = new AsyncEnumerator(async yield =>
55 | {
56 | using (testDisposable)
57 | {
58 | await yield.ReturnAsync(1);
59 | await yield.ReturnAsync(2);
60 | await yield.ReturnAsync(3);
61 | }
62 | });
63 |
64 | // ACT
65 |
66 | await enumerator.MoveNextAsync();
67 | enumerator.Dispose();
68 |
69 | // ASSERT
70 |
71 | Assert.IsTrue(testDisposable.HasDisposed);
72 | }
73 |
74 | [Test]
75 | public async Task DisposeByGCAfterPartialEnumeration()
76 | {
77 | // ARRANGE
78 |
79 | var testDisposable = new TestDisposable();
80 |
81 | void CreateEnumeratorAndMoveNext()
82 | {
83 | var enumerator = new AsyncEnumerator(async yield =>
84 | {
85 | using (testDisposable)
86 | {
87 | await yield.ReturnAsync(1);
88 | await yield.ReturnAsync(2);
89 | await yield.ReturnAsync(3);
90 | }
91 | });
92 |
93 | // Do partial enumeration.
94 | enumerator.MoveNextAsync().GetAwaiter().GetResult();
95 | }
96 |
97 | // ACT
98 | CreateEnumeratorAndMoveNext();
99 |
100 | // Instead of calling enumerator.Dispose(), do garbage collection.
101 | GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, blocking: true);
102 |
103 | // Give some time to other thread that does the disposal of the enumerator.
104 | // (see finalizer of the AsyncEnumerator for details)
105 | await Task.Delay(16);
106 |
107 | // ASSERT
108 |
109 | Assert.IsTrue(testDisposable.HasDisposed);
110 | }
111 |
112 | [Test]
113 | public void OnDisposeActionIsCalled()
114 | {
115 | // ARRANGE
116 |
117 | var testDisposable = new TestDisposable();
118 |
119 | var enumerator = new AsyncEnumerator(async yield =>
120 | {
121 | await yield.ReturnAsync(1);
122 | },
123 | onDispose: () => testDisposable.Dispose());
124 |
125 | // ACT
126 |
127 | enumerator.Dispose();
128 |
129 | // ASSERT
130 |
131 | Assert.IsTrue(testDisposable.HasDisposed);
132 | }
133 |
134 | [Test]
135 | public void OnDisposeMustBeCalledOnGcWhenEnumerationHasNotBeenStarted()
136 | {
137 | // ARRANGE
138 |
139 | var testDisposable = new TestDisposable();
140 |
141 | void CreateEnumerator()
142 | {
143 | var enumerator = new AsyncEnumerator(async yield =>
144 | {
145 | await yield.ReturnAsync(1);
146 | },
147 | onDispose: () => testDisposable.Dispose());
148 | }
149 |
150 | // ACT
151 |
152 | CreateEnumerator();
153 | GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, blocking: true);
154 | Thread.Sleep(16);
155 |
156 | // ASSERT
157 |
158 | Assert.IsTrue(testDisposable.HasDisposed);
159 | }
160 |
161 | [Test]
162 | public async Task DisposeWaitsForFinalization()
163 | {
164 | var tcs = new TaskCompletionSource