소스 검색

Move to BCL implementation of ConfigureAwait and WithCancellation.

Bart De Smet 6 년 전
부모
커밋
b27038cdfa

+ 0 - 77
Ix.NET/Source/System.Linq.Async/System/Linq/AsyncEnumerable.WithCancellation.cs

@@ -1,77 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the Apache 2.0 License.
-// See the LICENSE file in the project root for more information. 
-
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace System.Linq
-{
-    public static partial class AsyncEnumerable
-    {
-        // REVIEW: [LDM-2018-11-28] Should return type be a struct or just the interface type? Should this live in the System.Linq namespace or in System.Collections.Generic?
-
-        public static WithCancellationTokenAsyncEnumerable<T> WithCancellation<T>(this IAsyncEnumerable<T> source, CancellationToken cancellationToken)
-        {
-            if (source == null)
-                throw Error.ArgumentNull(nameof(source));
-
-            return new WithCancellationTokenAsyncEnumerable<T>(source, cancellationToken);
-        }
-
-        // REVIEW: Explicit implementation of the interfaces allows for composition with other "modifier operators" such as ConfigureAwait.
-        //         We expect that the "await foreach" statement will bind to the public struct methods, thus avoiding boxing.
-
-        public readonly struct WithCancellationTokenAsyncEnumerable<T> : IAsyncEnumerable<T>
-        {
-            private readonly IAsyncEnumerable<T> _source;
-            private readonly CancellationToken _cancellationToken;
-
-            public WithCancellationTokenAsyncEnumerable(IAsyncEnumerable<T> source, CancellationToken cancellationToken)
-            {
-                _source = source;
-                _cancellationToken = cancellationToken;
-            }
-
-            // REVIEW: Should we simply ignore the second cancellation token or should we link the two?
-            // REVIEW: [LDM-2018-11-28] Should we have eager cancellation here too?
-
-            public WithCancellationAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken)
-            {
-                CancellationToken token;
-
-                if (cancellationToken == default)
-                {
-                    token = _cancellationToken;
-                }
-                else if (_cancellationToken == default)
-                {
-                    token = cancellationToken;
-                }
-                else
-                {
-                    token = CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken, cancellationToken).Token;
-                }
-
-                return new WithCancellationAsyncEnumerator(_source.GetAsyncEnumerator(token));
-            }
-
-            IAsyncEnumerator<T> IAsyncEnumerable<T>.GetAsyncEnumerator(CancellationToken cancellationToken)
-                => GetAsyncEnumerator(cancellationToken);
-
-            public readonly struct WithCancellationAsyncEnumerator : IAsyncEnumerator<T>
-            {
-                private readonly IAsyncEnumerator<T> _enumerator;
-
-                public WithCancellationAsyncEnumerator(IAsyncEnumerator<T> enumerator) => _enumerator = enumerator;
-
-                public T Current => _enumerator.Current;
-
-                public ValueTask DisposeAsync() => _enumerator.DisposeAsync();
-
-                public ValueTask<bool> MoveNextAsync() => _enumerator.MoveNextAsync();
-            }
-        }
-    }
-}

+ 86 - 0
Ix.NET/Source/System.Linq.Async/System/Runtime/CompilerServices/ConfiguredCancelableAsyncEnumerable.cs

@@ -0,0 +1,86 @@
+#if !BCL_HAS_CONFIGUREAWAIT // https://github.com/dotnet/coreclr/pull/21939
+
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Runtime.CompilerServices
+{
+    /// <summary>Provides an awaitable async enumerable that enables cancelable iteration and configured awaits.</summary>
+    [StructLayout(LayoutKind.Auto)]
+    public readonly struct ConfiguredCancelableAsyncEnumerable<T>
+    {
+        private readonly IAsyncEnumerable<T> _enumerable;
+        private readonly CancellationToken _cancellationToken;
+        private readonly bool _continueOnCapturedContext;
+
+        internal ConfiguredCancelableAsyncEnumerable(IAsyncEnumerable<T> enumerable, bool continueOnCapturedContext, CancellationToken cancellationToken)
+        {
+            _enumerable = enumerable;
+            _continueOnCapturedContext = continueOnCapturedContext;
+            _cancellationToken = cancellationToken;
+        }
+
+        /// <summary>Configures how awaits on the tasks returned from an async iteration will be performed.</summary>
+        /// <param name="continueOnCapturedContext">Whether to capture and marshal back to the current context.</param>
+        /// <returns>The configured enumerable.</returns>
+        /// <remarks>This will replace any previous value set by <see cref="ConfigureAwait(bool)"/> for this iteration.</remarks>
+        public ConfiguredCancelableAsyncEnumerable<T> ConfigureAwait(bool continueOnCapturedContext) =>
+            new ConfiguredCancelableAsyncEnumerable<T>(_enumerable, continueOnCapturedContext, _cancellationToken);
+
+        /// <summary>Sets the <see cref="CancellationToken"/> to be passed to <see cref="IAsyncEnumerable{T}.GetAsyncEnumerator(CancellationToken)"/> when iterating.</summary>
+        /// <param name="cancellationToken">The <see cref="CancellationToken"/> to use.</param>
+        /// <returns>The configured enumerable.</returns>
+        /// <remarks>This will replace any previous <see cref="CancellationToken"/> set by <see cref="WithCancellation(CancellationToken)"/> for this iteration.</remarks>
+        public ConfiguredCancelableAsyncEnumerable<T> WithCancellation(CancellationToken cancellationToken) =>
+            new ConfiguredCancelableAsyncEnumerable<T>(_enumerable, _continueOnCapturedContext, cancellationToken);
+
+        public Enumerator GetAsyncEnumerator() =>
+            // as with other "configured" awaitable-related type in CompilerServices, we don't null check to defend against
+            // misuse like `default(ConfiguredCancelableAsyncEnumerable<T>).GetAsyncEnumerator()`, which will null ref by design.
+            new Enumerator(_enumerable.GetAsyncEnumerator(_cancellationToken), _continueOnCapturedContext);
+
+        /// <summary>Provides an awaitable async enumerator that enables cancelable iteration and configured awaits.</summary>
+        [StructLayout(LayoutKind.Auto)]
+        public readonly struct Enumerator : IAsyncDisposable // Workaround for https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-01-16.md
+        {
+            private readonly IAsyncEnumerator<T> _enumerator;
+            private readonly bool _continueOnCapturedContext;
+
+            internal Enumerator(IAsyncEnumerator<T> enumerator, bool continueOnCapturedContext)
+            {
+                _enumerator = enumerator;
+                _continueOnCapturedContext = continueOnCapturedContext;
+            }
+
+            /// <summary>Advances the enumerator asynchronously to the next element of the collection.</summary>
+            /// <returns>
+            /// A <see cref="ConfiguredValueTaskAwaitable{Boolean}"/> that will complete with a result of <c>true</c>
+            /// if the enumerator was successfully advanced to the next element, or <c>false</c> if the enumerator has
+            /// passed the end of the collection.
+            /// </returns>
+            public ConfiguredValueTaskAwaitable<bool> MoveNextAsync() =>
+                _enumerator.MoveNextAsync().ConfigureAwait(_continueOnCapturedContext);
+
+            /// <summary>Gets the element in the collection at the current position of the enumerator.</summary>
+            public T Current => _enumerator.Current;
+
+            /// <summary>
+            /// Performs application-defined tasks associated with freeing, releasing, or
+            /// resetting unmanaged resources asynchronously.
+            /// </summary>
+            public ConfiguredValueTaskAwaitable DisposeAsync() =>
+                _enumerator.DisposeAsync().ConfigureAwait(_continueOnCapturedContext);
+
+            async ValueTask IAsyncDisposable.DisposeAsync() =>
+                await _enumerator.DisposeAsync().ConfigureAwait(_continueOnCapturedContext);
+        }
+    }
+}
+
+#endif

+ 24 - 61
Ix.NET/Source/System.Linq.Async/System/Threading/Tasks/AsyncEnumerableExtensions.cs

@@ -9,71 +9,34 @@ namespace System.Threading.Tasks
 {
     public static class AsyncEnumerableExtensions
     {
-        public static ConfiguredAsyncEnumerable<T> ConfigureAwait<T>(this IAsyncEnumerable<T> enumerable, bool continueOnCapturedContext)
-        {
-            if (enumerable == null)
-                throw Error.ArgumentNull(nameof(enumerable));
-
-            return new ConfiguredAsyncEnumerable<T>(enumerable, continueOnCapturedContext);
-        }
-
-        public static ConfiguredAsyncEnumerable<T>.ConfiguredAsyncEnumerator ConfigureAwait<T>(this IAsyncEnumerator<T> enumerator, bool continueOnCapturedContext)
+#if !BCL_HAS_CONFIGUREAWAIT // https://github.com/dotnet/coreclr/pull/21939
+
+        /// <summary>Configures how awaits on the tasks returned from an async iteration will be performed.</summary>
+        /// <typeparam name="T">The type of the objects being iterated.</typeparam>
+        /// <param name="source">The source enumerable being iterated.</param>
+        /// <param name="continueOnCapturedContext">Whether to capture and marshal back to the current context.</param>
+        /// <returns>The configured enumerable.</returns>
+        public static ConfiguredCancelableAsyncEnumerable<T> ConfigureAwait<T>(
+            this IAsyncEnumerable<T> source, bool continueOnCapturedContext) =>
+            new ConfiguredCancelableAsyncEnumerable<T>(source, continueOnCapturedContext, cancellationToken: default);
+
+        /// <summary>Sets the <see cref="CancellationToken"/> to be passed to <see cref="IAsyncEnumerable{T}.GetAsyncEnumerator(CancellationToken)"/> when iterating.</summary>
+        /// <typeparam name="T">The type of the objects being iterated.</typeparam>
+        /// <param name="source">The source enumerable being iterated.</param>
+        /// <param name="cancellationToken">The <see cref="CancellationToken"/> to use.</param>
+        /// <returns>The configured enumerable.</returns>
+        public static ConfiguredCancelableAsyncEnumerable<T> WithCancellation<T>(
+            this IAsyncEnumerable<T> source, CancellationToken cancellationToken) =>
+            new ConfiguredCancelableAsyncEnumerable<T>(source, continueOnCapturedContext: true, cancellationToken);
+
+#endif
+
+        public static ConfiguredCancelableAsyncEnumerable<T>.Enumerator ConfigureAwait<T>(this IAsyncEnumerator<T> enumerator, bool continueOnCapturedContext)
         {
             if (enumerator == null)
                 throw Error.ArgumentNull(nameof(enumerator));
 
-            return new ConfiguredAsyncEnumerable<T>.ConfiguredAsyncEnumerator(enumerator, continueOnCapturedContext);
-        }
-
-        // REVIEW: Explicit implementation of the interfaces allows for composition with other "modifier operators" such as WithCancellation.
-        //         We expect that the "await foreach" statement will bind to the public struct methods, thus avoiding boxing.
-
-        public readonly struct ConfiguredAsyncEnumerable<T> : IAsyncEnumerable<T>
-        {
-            private readonly IAsyncEnumerable<T> _enumerable;
-            private readonly bool _continueOnCapturedContext;
-
-            internal ConfiguredAsyncEnumerable(IAsyncEnumerable<T> enumerable, bool continueOnCapturedContext)
-            {
-                _enumerable = enumerable;
-                _continueOnCapturedContext = continueOnCapturedContext;
-            }
-
-            public ConfiguredAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken)
-            {
-                cancellationToken.ThrowIfCancellationRequested(); // NB: [LDM-2018-11-28] Equivalent to async iterator behavior.
-
-                return new ConfiguredAsyncEnumerator(_enumerable.GetAsyncEnumerator(cancellationToken), _continueOnCapturedContext);
-            }
-
-            IAsyncEnumerator<T> IAsyncEnumerable<T>.GetAsyncEnumerator(CancellationToken cancellationToken) =>
-                GetAsyncEnumerator(cancellationToken);
-
-            public readonly struct ConfiguredAsyncEnumerator : IAsyncEnumerator<T>
-            {
-                private readonly IAsyncEnumerator<T> _enumerator;
-                private readonly bool _continueOnCapturedContext;
-
-                internal ConfiguredAsyncEnumerator(IAsyncEnumerator<T> enumerator, bool continueOnCapturedContext)
-                {
-                    _enumerator = enumerator;
-                    _continueOnCapturedContext = continueOnCapturedContext;
-                }
-
-                public ConfiguredValueTaskAwaitable<bool> MoveNextAsync() =>
-                    _enumerator.MoveNextAsync().ConfigureAwait(_continueOnCapturedContext);
-
-                public T Current => _enumerator.Current;
-
-                public ConfiguredValueTaskAwaitable DisposeAsync() =>
-                    _enumerator.DisposeAsync().ConfigureAwait(_continueOnCapturedContext);
-
-                async ValueTask<bool> IAsyncEnumerator<T>.MoveNextAsync() =>
-                    await _enumerator.MoveNextAsync().ConfigureAwait(_continueOnCapturedContext);
-
-                async ValueTask IAsyncDisposable.DisposeAsync() =>
-                    await _enumerator.DisposeAsync().ConfigureAwait(_continueOnCapturedContext);
-            }
+            return new ConfiguredCancelableAsyncEnumerable<T>.Enumerator(enumerator, continueOnCapturedContext);
         }
     }
 }