Add ManualResetValueTaskSourceCore / AsyncIterateMethodBuilder to Microsoft.Bcl.Async...
authorStephen Toub <stoub@microsoft.com>
Wed, 1 May 2019 15:57:35 +0000 (11:57 -0400)
committerGitHub <noreply@github.com>
Wed, 1 May 2019 15:57:35 +0000 (11:57 -0400)
* Add ManualResetValueTaskSourceCore / AsyncIterateMethodBuilder to Microsoft.Bcl.AsyncInterfaces

These two types needed modifications to target .NET Standard 2.0 and are the necessary pieces to enable the compiler to compile async iterators.

- Copied ManualResetValueTaskSourceCore.cs from coreclr and tweaked it.  I opted to do this rather than ifdef because the changes are not localized and I didn't want to significantly perturb the primary implementation.
- Added a few ifdefs to the shared AsyncIteratorMethodBuilder.  It already had ifdefs, so I just added to it.
- Added a test project, and included the existing ManualResetValueTaskSourceCore tests.  I had to disable two of the tests because of some of the optimization differences.
- Augmented those tests to validate that the compiler is able to successfully generate iterators and await foreach them.

* Address PR feedback

Commit migrated from https://github.com/dotnet/corefx/commit/07b6760a1a84251a50f744e585e4da5f71a25e68

src/libraries/Microsoft.Bcl.AsyncInterfaces/ref/Microsoft.Bcl.AsyncInterfaces.Forwards.cs
src/libraries/Microsoft.Bcl.AsyncInterfaces/ref/Microsoft.Bcl.AsyncInterfaces.cs
src/libraries/Microsoft.Bcl.AsyncInterfaces/ref/Microsoft.Bcl.AsyncInterfaces.csproj
src/libraries/Microsoft.Bcl.AsyncInterfaces/src/Microsoft.Bcl.AsyncInterfaces.csproj
src/libraries/Microsoft.Bcl.AsyncInterfaces/src/System/Runtime/CompilerServices/AsyncIteratorMethodBuilder.cs [new file with mode: 0644]
src/libraries/Microsoft.Bcl.AsyncInterfaces/src/System/Threading/Tasks/Sources/ManualResetValueTaskSourceCore.cs [new file with mode: 0644]
src/libraries/Microsoft.Bcl.AsyncInterfaces/tests/Configurations.props [new file with mode: 0644]
src/libraries/Microsoft.Bcl.AsyncInterfaces/tests/Microsoft.Bcl.AsyncInterfaces.Tests.csproj [new file with mode: 0644]
src/libraries/System.Threading.Tasks.Extensions/tests/ManualResetValueTaskSourceTests.cs

index 8f737d6..6b61d3d 100644 (file)
@@ -5,6 +5,8 @@
 [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.IAsyncDisposable))]
 [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Collections.Generic.IAsyncEnumerable<>))]
 [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Collections.Generic.IAsyncEnumerator<>))]
+[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.AsyncIteratorMethodBuilder))]
 [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.AsyncIteratorStateMachineAttribute))]
 [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.ConfiguredAsyncDisposable))]
 [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable<>))]
+[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(System.Threading.Tasks.Sources.ManualResetValueTaskSourceCore<>))]
index fefe5c5..caa86d3 100644 (file)
@@ -26,6 +26,15 @@ namespace System.Collections.Generic
 }
 namespace System.Runtime.CompilerServices
 {
+    public partial struct AsyncIteratorMethodBuilder
+    {
+        private object _dummy;
+        public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : System.Runtime.CompilerServices.INotifyCompletion where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine { }
+        public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine { }
+        public void Complete() { }
+        public static System.Runtime.CompilerServices.AsyncIteratorMethodBuilder Create() { throw null; }
+        public void MoveNext<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine { }
+    }
     [System.AttributeUsageAttribute(System.AttributeTargets.Method, Inherited=false, AllowMultiple=false)]
     public sealed partial class AsyncIteratorStateMachineAttribute : System.Runtime.CompilerServices.StateMachineAttribute
     {
@@ -54,3 +63,20 @@ namespace System.Runtime.CompilerServices
         }
     }
 }
+namespace System.Threading.Tasks.Sources
+{
+    public partial struct ManualResetValueTaskSourceCore<TResult>
+    {
+        private TResult _result;
+        private object _dummy;
+        private int _dummyPrimitive;
+        public bool RunContinuationsAsynchronously { get { throw null; } set { } }
+        public short Version { get { throw null; } }
+        public TResult GetResult(short token) { throw null; }
+        public System.Threading.Tasks.Sources.ValueTaskSourceStatus GetStatus(short token) { throw null; }
+        public void OnCompleted(System.Action<object> continuation, object state, short token, System.Threading.Tasks.Sources.ValueTaskSourceOnCompletedFlags flags) { }
+        public void Reset() { }
+        public void SetException(System.Exception error) { }
+        public void SetResult(TResult result) { }
+    }
+}
index 3c58faf..e61b496 100644 (file)
@@ -13,5 +13,6 @@
   <ItemGroup Condition="'$(TargetGroup)' == 'netstandard'">
     <Reference Include="System.Runtime" />
     <Reference Include="System.Threading.Tasks" />
+    <Reference Include="System.Threading.Tasks.Extensions" />
   </ItemGroup>
 </Project>
\ No newline at end of file
index 2e98b39..798c0e1 100644 (file)
@@ -5,6 +5,8 @@
     <IsPartialFacadeAssembly Condition="'$(TargetGroup)' != 'netstandard'">true</IsPartialFacadeAssembly>
   </PropertyGroup>
   <ItemGroup Condition="'$(IsPartialFacadeAssembly)' != 'true'">
+    <Compile Include="System\Threading\Tasks\Sources\ManualResetValueTaskSourceCore.cs" />
+    <Compile Include="System\Runtime\CompilerServices\AsyncIteratorMethodBuilder.cs" />
     <Compile Include="$(CommonPath)\CoreLib\System\Collections\Generic\IAsyncEnumerable.cs">
       <Link>ProductionCode\Common\CoreLib\System\Collections\Generic\IAsyncEnumerable.cs</Link>
     </Compile>
@@ -26,7 +28,7 @@
   </ItemGroup>
   <ItemGroup Condition="'$(TargetGroup)' == 'netstandard'">
     <Reference Include="System.Runtime" />
-    <Reference Include="System.Threading.Tasks.Extensions" />
     <Reference Include="System.Threading.Tasks" />
+    <Reference Include="System.Threading.Tasks.Extensions" />
   </ItemGroup>
 </Project>
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Bcl.AsyncInterfaces/src/System/Runtime/CompilerServices/AsyncIteratorMethodBuilder.cs b/src/libraries/Microsoft.Bcl.AsyncInterfaces/src/System/Runtime/CompilerServices/AsyncIteratorMethodBuilder.cs
new file mode 100644 (file)
index 0000000..08e8e31
--- /dev/null
@@ -0,0 +1,62 @@
+// 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.
+
+// NOTE: This is a copy of
+// https://github.com/dotnet/coreclr/blame/07b3afc27304800f00975c8fd4836b319aaa8820/src/System.Private.CoreLib/shared/System/Runtime/CompilerServices/AsyncIteratorMethodBuilder.cs
+// modified to be compilable against .NET Standard 2.0.  Key differences:
+// - Uses the wrapped AsyncTaskMethodBuilder for Create and MoveNext.
+// - Uses a custom object for the debugger identity.
+// - Nullable annotations removed.
+
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace System.Runtime.CompilerServices
+{
+    /// <summary>Represents a builder for asynchronous iterators.</summary>
+    [StructLayout(LayoutKind.Auto)]
+    public struct AsyncIteratorMethodBuilder
+    {
+        private AsyncTaskMethodBuilder _methodBuilder; // mutable struct; do not make it readonly
+        private object _id;
+
+        /// <summary>Creates an instance of the <see cref="AsyncIteratorMethodBuilder"/> struct.</summary>
+        /// <returns>The initialized instance.</returns>
+        public static AsyncIteratorMethodBuilder Create() =>
+            new AsyncIteratorMethodBuilder() { _methodBuilder = AsyncTaskMethodBuilder.Create() };
+
+        /// <summary>Invokes <see cref="IAsyncStateMachine.MoveNext"/> on the state machine while guarding the <see cref="ExecutionContext"/>.</summary>
+        /// <typeparam name="TStateMachine">The type of the state machine.</typeparam>
+        /// <param name="stateMachine">The state machine instance, passed by reference.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void MoveNext<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine =>
+            _methodBuilder.Start(ref stateMachine);
+
+        /// <summary>Schedules the state machine to proceed to the next action when the specified awaiter completes.</summary>
+        /// <typeparam name="TAwaiter">The type of the awaiter.</typeparam>
+        /// <typeparam name="TStateMachine">The type of the state machine.</typeparam>
+        /// <param name="awaiter">The awaiter.</param>
+        /// <param name="stateMachine">The state machine.</param>
+        public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
+            where TAwaiter : INotifyCompletion
+            where TStateMachine : IAsyncStateMachine =>
+            _methodBuilder.AwaitOnCompleted(ref awaiter, ref stateMachine);
+
+        /// <summary>Schedules the state machine to proceed to the next action when the specified awaiter completes.</summary>
+        /// <typeparam name="TAwaiter">The type of the awaiter.</typeparam>
+        /// <typeparam name="TStateMachine">The type of the state machine.</typeparam>
+        /// <param name="awaiter">The awaiter.</param>
+        /// <param name="stateMachine">The state machine.</param>
+        public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
+            where TAwaiter : ICriticalNotifyCompletion
+            where TStateMachine : IAsyncStateMachine =>
+            _methodBuilder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
+
+        /// <summary>Marks iteration as being completed, whether successfully or otherwise.</summary>
+        public void Complete() => _methodBuilder.SetResult();
+
+        /// <summary>Gets an object that may be used to uniquely identify this builder to the debugger.</summary>
+        internal object ObjectIdForDebugger => _id ?? Interlocked.CompareExchange(ref _id, new object(), null) ?? _id; 
+    }
+}
diff --git a/src/libraries/Microsoft.Bcl.AsyncInterfaces/src/System/Threading/Tasks/Sources/ManualResetValueTaskSourceCore.cs b/src/libraries/Microsoft.Bcl.AsyncInterfaces/src/System/Threading/Tasks/Sources/ManualResetValueTaskSourceCore.cs
new file mode 100644 (file)
index 0000000..06d0da1
--- /dev/null
@@ -0,0 +1,272 @@
+// 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.
+
+// NOTE: This is a copy of
+// https://github.com/dotnet/coreclr/blame/07b3afc27304800f00975c8fd4836b319aaa8820/src/System.Private.CoreLib/shared/System/Threading/Tasks/Sources/ManualResetValueTaskSourceCore.cs,
+// modified to be compilable against .NET Standard 2.0.  It is missing optimizations present in the .NET Core implementation and should
+// only be used when a .NET Standard 2.0 implementation is required.  Key differences:
+// - ThrowHelper call sites are replaced by normal exception throws.
+// - ThreadPool.{Unsafe}QueueUserWorkItem calls that accepted Action<object>/object/bool arguments are replaced by Task.Factory.StartNew usage.
+// - ExecutionContext.RunInternal are replaced by ExecutionContext.Run.
+// - Nullability annotations are removed.
+
+using System.Diagnostics;
+using System.Runtime.ExceptionServices;
+using System.Runtime.InteropServices;
+
+namespace System.Threading.Tasks.Sources
+{
+    /// <summary>Provides the core logic for implementing a manual-reset <see cref="IValueTaskSource"/> or <see cref="IValueTaskSource{TResult}"/>.</summary>
+    /// <typeparam name="TResult"></typeparam>
+    [StructLayout(LayoutKind.Auto)]
+    public struct ManualResetValueTaskSourceCore<TResult>
+    {
+        /// <summary>
+        /// The callback to invoke when the operation completes if <see cref="OnCompleted"/> was called before the operation completed,
+        /// or <see cref="ManualResetValueTaskSourceCoreShared.s_sentinel"/> if the operation completed before a callback was supplied,
+        /// or null if a callback hasn't yet been provided and the operation hasn't yet completed.
+        /// </summary>
+        private Action<object> _continuation;
+        /// <summary>State to pass to <see cref="_continuation"/>.</summary>
+        private object _continuationState;
+        /// <summary><see cref="ExecutionContext"/> to flow to the callback, or null if no flowing is required.</summary>
+        private ExecutionContext _executionContext;
+        /// <summary>
+        /// A "captured" <see cref="SynchronizationContext"/> or <see cref="TaskScheduler"/> with which to invoke the callback,
+        /// or null if no special context is required.
+        /// </summary>
+        private object _capturedContext;
+        /// <summary>Whether the current operation has completed.</summary>
+        private bool _completed;
+        /// <summary>The result with which the operation succeeded, or the default value if it hasn't yet completed or failed.</summary>
+        private TResult _result;
+        /// <summary>The exception with which the operation failed, or null if it hasn't yet completed or completed successfully.</summary>
+        private ExceptionDispatchInfo _error;
+        /// <summary>The current version of this value, used to help prevent misuse.</summary>
+        private short _version;
+
+        /// <summary>Gets or sets whether to force continuations to run asynchronously.</summary>
+        /// <remarks>Continuations may run asynchronously if this is false, but they'll never run synchronously if this is true.</remarks>
+        public bool RunContinuationsAsynchronously { get; set; }
+
+        /// <summary>Resets to prepare for the next operation.</summary>
+        public void Reset()
+        {
+            // Reset/update state for the next use/await of this instance.
+            _version++;
+            _completed = false;
+            _result = default!; // TODO-NULLABLE-GENERIC
+            _error = null;
+            _executionContext = null;
+            _capturedContext = null;
+            _continuation = null;
+            _continuationState = null;
+        }
+
+        /// <summary>Completes with a successful result.</summary>
+        /// <param name="result">The result.</param>
+        public void SetResult(TResult result)
+        {
+            _result = result;
+            SignalCompletion();
+        }
+
+        /// <summary>Complets with an error.</summary>
+        /// <param name="error"></param>
+        public void SetException(Exception error)
+        {
+            _error = ExceptionDispatchInfo.Capture(error);
+            SignalCompletion();
+        }
+
+        /// <summary>Gets the operation version.</summary>
+        public short Version => _version;
+
+        /// <summary>Gets the status of the operation.</summary>
+        /// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param>
+        public ValueTaskSourceStatus GetStatus(short token)
+        {
+            ValidateToken(token);
+            return
+                _continuation == null || !_completed ? ValueTaskSourceStatus.Pending :
+                _error == null ? ValueTaskSourceStatus.Succeeded :
+                _error.SourceException is OperationCanceledException ? ValueTaskSourceStatus.Canceled :
+                ValueTaskSourceStatus.Faulted;
+        }
+
+        /// <summary>Gets the result of the operation.</summary>
+        /// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param>
+        public TResult GetResult(short token)
+        {
+            ValidateToken(token);
+            if (!_completed)
+            {
+                throw new InvalidOperationException();
+            }
+
+            _error?.Throw();
+            return _result;
+        }
+
+        /// <summary>Schedules the continuation action for this operation.</summary>
+        /// <param name="continuation">The continuation to invoke when the operation has completed.</param>
+        /// <param name="state">The state object to pass to <paramref name="continuation"/> when it's invoked.</param>
+        /// <param name="token">Opaque value that was provided to the <see cref="ValueTask"/>'s constructor.</param>
+        /// <param name="flags">The flags describing the behavior of the continuation.</param>
+        public void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) // TODO-NULLABLE: https://github.com/dotnet/roslyn/issues/26761
+        {
+            if (continuation == null)
+            {
+                throw new ArgumentNullException(nameof(continuation));
+            }
+            ValidateToken(token);
+
+            if ((flags & ValueTaskSourceOnCompletedFlags.FlowExecutionContext) != 0)
+            {
+                _executionContext = ExecutionContext.Capture();
+            }
+
+            if ((flags & ValueTaskSourceOnCompletedFlags.UseSchedulingContext) != 0)
+            {
+                SynchronizationContext sc = SynchronizationContext.Current;
+                if (sc != null && sc.GetType() != typeof(SynchronizationContext))
+                {
+                    _capturedContext = sc;
+                }
+                else
+                {
+                    TaskScheduler ts = TaskScheduler.Current;
+                    if (ts != TaskScheduler.Default)
+                    {
+                        _capturedContext = ts;
+                    }
+                }
+            }
+
+            // We need to set the continuation state before we swap in the delegate, so that
+            // if there's a race between this and SetResult/Exception and SetResult/Exception
+            // sees the _continuation as non-null, it'll be able to invoke it with the state
+            // stored here.  However, this also means that if this is used incorrectly (e.g.
+            // awaited twice concurrently), _continuationState might get erroneously overwritten.
+            // To minimize the chances of that, we check preemptively whether _continuation
+            // is already set to something other than the completion sentinel.
+
+            object oldContinuation = _continuation;
+            if (oldContinuation == null)
+            {
+                _continuationState = state;
+                oldContinuation = Interlocked.CompareExchange(ref _continuation, continuation, null);
+            }
+
+            if (oldContinuation != null)
+            {
+                // Operation already completed, so we need to queue the supplied callback.
+                if (!ReferenceEquals(oldContinuation, ManualResetValueTaskSourceCoreShared.s_sentinel))
+                {
+                    throw new InvalidOperationException();
+                }
+
+                switch (_capturedContext)
+                {
+                    case null:
+                        Task.Factory.StartNew(continuation, state, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+                        break;
+
+                    case SynchronizationContext sc:
+                        sc.Post(s =>
+                        {
+                            var tuple = (Tuple<Action<object>, object>)s;
+                            tuple.Item1(tuple.Item2);
+                        }, Tuple.Create(continuation, state));
+                        break;
+
+                    case TaskScheduler ts:
+                        Task.Factory.StartNew(continuation, state, CancellationToken.None, TaskCreationOptions.DenyChildAttach, ts);
+                        break;
+                }
+            }
+        }
+
+        /// <summary>Ensures that the specified token matches the current version.</summary>
+        /// <param name="token">The token supplied by <see cref="ValueTask"/>.</param>
+        private void ValidateToken(short token)
+        {
+            if (token != _version)
+            {
+                throw new InvalidOperationException();
+            }
+        }
+
+        /// <summary>Signals that the operation has completed.  Invoked after the result or error has been set.</summary>
+        private void SignalCompletion()
+        {
+            if (_completed)
+            {
+                throw new InvalidOperationException();
+            }
+            _completed = true;
+
+            if (_continuation != null || Interlocked.CompareExchange(ref _continuation, ManualResetValueTaskSourceCoreShared.s_sentinel, null) != null)
+            {
+                if (_executionContext != null)
+                {
+                    ExecutionContext.Run(
+                        _executionContext,
+                        s => ((ManualResetValueTaskSourceCore<TResult>)s).InvokeContinuation(),
+                        this);
+                }
+                else
+                {
+                    InvokeContinuation();
+                }
+            }
+        }
+
+        /// <summary>
+        /// Invokes the continuation with the appropriate captured context / scheduler.
+        /// This assumes that if <see cref="_executionContext"/> is not null we're already
+        /// running within that <see cref="ExecutionContext"/>.
+        /// </summary>
+        private void InvokeContinuation()
+        {
+            Debug.Assert(_continuation != null);
+
+            switch (_capturedContext)
+            {
+                case null:
+                    if (RunContinuationsAsynchronously)
+                    {
+                        Task.Factory.StartNew(_continuation, _continuationState, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
+                    }
+                    else
+                    {
+                        _continuation(_continuationState);
+                    }
+                    break;
+
+                case SynchronizationContext sc:
+                    sc.Post(s =>
+                    {
+                        var state = (Tuple<Action<object>, object>)s;
+                        state.Item1(state.Item2);
+                    }, Tuple.Create(_continuation, _continuationState));
+                    break;
+
+                case TaskScheduler ts:
+                    Task.Factory.StartNew(_continuation, _continuationState, CancellationToken.None, TaskCreationOptions.DenyChildAttach, ts);
+                    break;
+            }
+        }
+    }
+
+    internal static class ManualResetValueTaskSourceCoreShared // separated out of generic to avoid unnecessary duplication
+    {
+        internal static readonly Action<object> s_sentinel = CompletionSentinel;
+        private static void CompletionSentinel(object _) // named method to aid debugging
+        {
+            Debug.Fail("The sentinel delegate should never be invoked.");
+            throw new InvalidOperationException();
+        }
+    }
+}
diff --git a/src/libraries/Microsoft.Bcl.AsyncInterfaces/tests/Configurations.props b/src/libraries/Microsoft.Bcl.AsyncInterfaces/tests/Configurations.props
new file mode 100644 (file)
index 0000000..581054d
--- /dev/null
@@ -0,0 +1,7 @@
+<Project DefaultTargets="Build">
+  <PropertyGroup>
+    <BuildConfigurations>
+      netstandard;
+    </BuildConfigurations>
+  </PropertyGroup>
+</Project>
\ No newline at end of file
diff --git a/src/libraries/Microsoft.Bcl.AsyncInterfaces/tests/Microsoft.Bcl.AsyncInterfaces.Tests.csproj b/src/libraries/Microsoft.Bcl.AsyncInterfaces/tests/Microsoft.Bcl.AsyncInterfaces.Tests.csproj
new file mode 100644 (file)
index 0000000..572955e
--- /dev/null
@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <ProjectGuid>{72E21903-0FBA-444E-9855-3B4F05DFC1F9}</ProjectGuid>
+    <Configurations>netstandard-Debug;netstandard-Release</Configurations>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="..\..\Common\tests\System\Threading\Tasks\Sources\ManualResetValueTaskSource.cs">
+      <Link>Common\tests\System\Threading\Tasks\Sources\ManualResetValueTaskSource.cs</Link>
+    </Compile>
+    <Compile Include="..\..\System.Threading.Tasks.Extensions\tests\ManualResetValueTaskSourceTests.cs">
+      <Link>System.Threading.Tasks.Extensions\tests\ManualResetValueTaskSourceTests.cs</Link>
+    </Compile>
+  </ItemGroup>
+</Project>
\ No newline at end of file
index 9242c2c..15d7f1c 100644 (file)
@@ -169,6 +169,7 @@ namespace System.Threading.Tasks.Sources.Tests
             Assert.Same(e, Assert.Throws<OperationCanceledException>(() => mrvts.GetResult(0)));
         }
 
+        [SkipOnTargetFramework(~TargetFrameworkMonikers.Netcoreapp)]
         [Theory]
         [InlineData(false)]
         [InlineData(true)]
@@ -192,6 +193,7 @@ namespace System.Threading.Tasks.Sources.Tests
             mres.Wait();
         }
 
+        [SkipOnTargetFramework(~TargetFrameworkMonikers.Netcoreapp)]
         [Theory]
         [InlineData(false)]
         [InlineData(true)]
@@ -399,43 +401,55 @@ namespace System.Threading.Tasks.Sources.Tests
             protected override IEnumerable<Task> GetScheduledTasks() => null;
         }
 
-        [Fact]
-        public async Task AsyncEnumerable_Success()
+        [Theory]
+        [InlineData(false, false)]
+        [InlineData(false, true)]
+        [InlineData(true, false)]
+        [InlineData(true, true)]
+        public async Task AsyncEnumerable_Success(bool awaitForeach, bool asyncIterator)
         {
-            // Equivalent to:
-            //   int total = 0;
-            //   foreach async(int i in CountAsync(20))
-            //   {
-            //       total += i;
-            //   }
-            //   Assert.Equal(190, i);
-
-            IAsyncEnumerator<int> enumerator = CountAsync(20).GetAsyncEnumerator();
-            try
+            IAsyncEnumerable<int> enumerable = asyncIterator ?
+                CountCompilerAsync(20) :
+                CountManualAsync(20);
+
+            if (awaitForeach)
             {
                 int total = 0;
-                while (await enumerator.MoveNextAsync())
+                await foreach (int i in enumerable)
                 {
-                    total += enumerator.Current;
+                    total += i;
                 }
                 Assert.Equal(190, total);
             }
-            finally
+            else
+            {
+                IAsyncEnumerator<int> enumerator = enumerable.GetAsyncEnumerator();
+                try
+                {
+                    int total = 0;
+                    while (await enumerator.MoveNextAsync())
+                    {
+                        total += enumerator.Current;
+                    }
+                    Assert.Equal(190, total);
+                }
+                finally
+                {
+                    await enumerator.DisposeAsync();
+                }
+            }
+        }
+
+        internal static async IAsyncEnumerable<int> CountCompilerAsync(int items)
+        {
+            for (int i = 0; i < items; i++)
             {
-                await enumerator.DisposeAsync();
+                await Task.Delay(i).ConfigureAwait(false);
+                yield return i;
             }
         }
 
-        // Approximate compiler-generated code for:
-        //     internal static AsyncEnumerable<int> CountAsync(int items)
-        //     {
-        //         for (int i = 0; i < items; i++)
-        //         {
-        //             await Task.Delay(i).ConfigureAwait(false);
-        //             yield return i;
-        //         }
-        //     }
-        internal static IAsyncEnumerable<int> CountAsync(int items) =>
+        internal static IAsyncEnumerable<int> CountManualAsync(int items) =>
             new CountAsyncEnumerable(items);
 
         private sealed class CountAsyncEnumerable :