From ae7bca43f10419e300e1084c0478985d8405cc7e Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Mon, 29 Oct 2018 09:00:11 -0400 Subject: [PATCH] Use new ManualResetValueTaskSourceCore in tests, and add tests --- .../Sources/ManualResetValueTaskSource.cs | 240 +---------- .../ManualResetValueTaskSourceFactory.cs | 3 +- .../tests/AsyncValueTaskMethodBuilderTests.cs | 1 + .../tests/ManualResetValueTaskSourceTests.cs | 390 ++++++++++++++++-- .../tests/Performance/Perf.ValueTask.cs | 2 +- ...em.Threading.Tasks.Extensions.Tests.csproj | 4 +- .../tests/ValueTaskTests.cs | 1 + 7 files changed, 374 insertions(+), 267 deletions(-) diff --git a/src/Common/tests/System/Threading/Tasks/Sources/ManualResetValueTaskSource.cs b/src/Common/tests/System/Threading/Tasks/Sources/ManualResetValueTaskSource.cs index e14e6f1f07af..bb028c923dc8 100644 --- a/src/Common/tests/System/Threading/Tasks/Sources/ManualResetValueTaskSource.cs +++ b/src/Common/tests/System/Threading/Tasks/Sources/ManualResetValueTaskSource.cs @@ -2,235 +2,21 @@ // 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.Runtime.CompilerServices; -using System.Runtime.ExceptionServices; - -namespace System.Runtime.CompilerServices -{ - public interface IStrongBox - { - ref T Value { get; } - } -} - namespace System.Threading.Tasks.Sources { - public sealed class ManualResetValueTaskSource : IStrongBox>, IValueTaskSource, IValueTaskSource + public sealed class ManualResetValueTaskSource : IValueTaskSource, IValueTaskSource { - private ManualResetValueTaskSourceLogic _logic; // mutable struct; do not make this readonly - - public ManualResetValueTaskSource() => _logic = new ManualResetValueTaskSourceLogic(this); - - public short Version => _logic.Version; - - public void Reset() => _logic.Reset(); - - public void SetResult(T result) => _logic.SetResult(result); - - public void SetException(Exception error) => _logic.SetException(error); - - public T GetResult(short token) => _logic.GetResult(token); - void IValueTaskSource.GetResult(short token) => _logic.GetResult(token); - - public ValueTaskSourceStatus GetStatus(short token) => _logic.GetStatus(token); - - public void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) => _logic.OnCompleted(continuation, state, token, flags); - - ref ManualResetValueTaskSourceLogic IStrongBox>.Value => ref _logic; - } - - public struct ManualResetValueTaskSourceLogic - { - private static readonly Action s_sentinel = new Action(s => throw new InvalidOperationException()); - - private readonly IStrongBox> _parent; - private Action _continuation; - private object _continuationState; - private object _capturedContext; - private ExecutionContext _executionContext; - private bool _completed; - private TResult _result; - private ExceptionDispatchInfo _error; - private short _version; - - public ManualResetValueTaskSourceLogic(IStrongBox> parent) - { - _parent = parent ?? throw new ArgumentNullException(nameof(parent)); - _continuation = null; - _continuationState = null; - _capturedContext = null; - _executionContext = null; - _completed = false; - _result = default; - _error = null; - _version = 0; - } - - public short Version => _version; - - private void ValidateToken(short token) - { - if (token != _version) - { - throw new InvalidOperationException(); - } - } - - public ValueTaskSourceStatus GetStatus(short token) - { - ValidateToken(token); - - return - !_completed ? ValueTaskSourceStatus.Pending : - _error == null ? ValueTaskSourceStatus.Succeeded : - _error.SourceException is OperationCanceledException ? ValueTaskSourceStatus.Canceled : - ValueTaskSourceStatus.Faulted; - } - - public TResult GetResult(short token) - { - ValidateToken(token); - - if (!_completed) - { - throw new InvalidOperationException(); - } - - _error?.Throw(); - return _result; - } - - public void Reset() - { - _version++; - - _completed = false; - _continuation = null; - _continuationState = null; - _result = default; - _error = null; - _executionContext = null; - _capturedContext = null; - } - - public void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) - { - 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; - } - } - } - - _continuationState = state; - if (Interlocked.CompareExchange(ref _continuation, continuation, null) != null) - { - _executionContext = null; - - object cc = _capturedContext; - _capturedContext = null; - - switch (cc) - { - case null: - Task.Factory.StartNew(continuation, state, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); - break; - - case SynchronizationContext sc: - sc.Post(s => - { - var tuple = (Tuple, 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; - } - } - } - - public void SetResult(TResult result) - { - _result = result; - SignalCompletion(); - } - - public void SetException(Exception error) - { - _error = ExceptionDispatchInfo.Capture(error); - SignalCompletion(); - } - - private void SignalCompletion() - { - if (_completed) - { - throw new InvalidOperationException(); - } - _completed = true; - - if (Interlocked.CompareExchange(ref _continuation, s_sentinel, null) != null) - { - if (_executionContext != null) - { - ExecutionContext.Run( - _executionContext, - s => ((IStrongBox>)s).Value.InvokeContinuation(), - _parent ?? throw new InvalidOperationException()); - } - else - { - InvokeContinuation(); - } - } - } - - private void InvokeContinuation() - { - object cc = _capturedContext; - _capturedContext = null; - - switch (cc) - { - case null: - _continuation(_continuationState); - break; - - case SynchronizationContext sc: - sc.Post(s => - { - ref ManualResetValueTaskSourceLogic logicRef = ref ((IStrongBox>)s).Value; - logicRef._continuation(logicRef._continuationState); - }, _parent ?? throw new InvalidOperationException()); - break; - - case TaskScheduler ts: - Task.Factory.StartNew(_continuation, _continuationState, CancellationToken.None, TaskCreationOptions.DenyChildAttach, ts); - break; - } - } + private ManualResetValueTaskSourceCore _core; // mutable struct; do not make this readonly + + public bool RunContinuationsAsynchronously { get => _core.RunContinuationsAsynchronously; set => _core.RunContinuationsAsynchronously = value; } + public short Version => _core.Version; + public void Reset() => _core.Reset(); + public void SetResult(T result) => _core.SetResult(result); + public void SetException(Exception error) => _core.SetException(error); + + public T GetResult(short token) => _core.GetResult(token); + void IValueTaskSource.GetResult(short token) => _core.GetResult(token); + public ValueTaskSourceStatus GetStatus(short token) => _core.GetStatus(token); + public void OnCompleted(Action continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags) => _core.OnCompleted(continuation, state, token, flags); } } diff --git a/src/Common/tests/System/Threading/Tasks/Sources/ManualResetValueTaskSourceFactory.cs b/src/Common/tests/System/Threading/Tasks/Sources/ManualResetValueTaskSourceFactory.cs index 6409a38afda3..3289c399813a 100644 --- a/src/Common/tests/System/Threading/Tasks/Sources/ManualResetValueTaskSourceFactory.cs +++ b/src/Common/tests/System/Threading/Tasks/Sources/ManualResetValueTaskSourceFactory.cs @@ -3,9 +3,8 @@ // See the LICENSE file in the project root for more information. using System.Runtime.ExceptionServices; -using System.Threading.Tasks.Sources; -namespace System.Threading.Tasks.Tests +namespace System.Threading.Tasks.Sources.Tests { internal static class ManualResetValueTaskSourceFactory { diff --git a/src/System.Threading.Tasks.Extensions/tests/AsyncValueTaskMethodBuilderTests.cs b/src/System.Threading.Tasks.Extensions/tests/AsyncValueTaskMethodBuilderTests.cs index 41fb7b441b33..0deeeb40c50d 100644 --- a/src/System.Threading.Tasks.Extensions/tests/AsyncValueTaskMethodBuilderTests.cs +++ b/src/System.Threading.Tasks.Extensions/tests/AsyncValueTaskMethodBuilderTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Runtime.CompilerServices; +using System.Threading.Tasks.Sources.Tests; using Xunit; namespace System.Threading.Tasks.Tests diff --git a/src/System.Threading.Tasks.Extensions/tests/ManualResetValueTaskSourceTests.cs b/src/System.Threading.Tasks.Extensions/tests/ManualResetValueTaskSourceTests.cs index 3ac3bb131361..b1c2cddb3b1a 100644 --- a/src/System.Threading.Tasks.Extensions/tests/ManualResetValueTaskSourceTests.cs +++ b/src/System.Threading.Tasks.Extensions/tests/ManualResetValueTaskSourceTests.cs @@ -2,6 +2,7 @@ // 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.CompilerServices; using Xunit; @@ -25,13 +26,18 @@ public async Task ReuseInstanceWithResets_Success() } [Fact] - public void AccessAfterReset_Fails() + public void AccessWrongVersion_Fails() { var mrvts = new ManualResetValueTaskSource(); mrvts.Reset(); + Assert.Throws(() => mrvts.GetResult(0)); Assert.Throws(() => mrvts.GetStatus(0)); Assert.Throws(() => mrvts.OnCompleted(_ => { }, new object(), 0, ValueTaskSourceOnCompletedFlags.None)); + + Assert.Throws(() => mrvts.GetResult(2)); + Assert.Throws(() => mrvts.GetStatus(2)); + Assert.Throws(() => mrvts.OnCompleted(_ => { }, new object(), 2, ValueTaskSourceOnCompletedFlags.None)); } [Fact] @@ -49,6 +55,348 @@ public void SetTwice_Fails() Assert.Throws(() => mrvts.SetException(new Exception())); } + [Fact] + public void GetResult_BeforeCompleted_Fails() + { + var mrvts = new ManualResetValueTaskSource(); + Assert.Throws(() => mrvts.GetResult(0)); + } + + [Fact] + public void SetResult_BeforeOnCompleted_ResultAvailableSynchronously() + { + var mrvts = new ManualResetValueTaskSource(); + mrvts.Reset(); + mrvts.Reset(); + Assert.Equal(2, mrvts.Version); + + mrvts.SetResult(42); + + Assert.Equal(ValueTaskSourceStatus.Succeeded, mrvts.GetStatus(2)); + Assert.Equal(42, mrvts.GetResult(2)); + + var mres = new ManualResetEventSlim(); + mrvts.OnCompleted(s => ((ManualResetEventSlim)s).Set(), mres, 2, ValueTaskSourceOnCompletedFlags.None); + mres.Wait(); + + Assert.Equal(2, mrvts.Version); + } + + [Fact] + public async Task SetResult_AfterOnCompleted_ResultAvailableAsynchronously() + { + var mrvts = new ManualResetValueTaskSource(); + mrvts.Reset(); + mrvts.Reset(); + Assert.Equal(2, mrvts.Version); + + Assert.Equal(ValueTaskSourceStatus.Pending, mrvts.GetStatus(2)); + Assert.Throws(() => mrvts.GetResult(2)); + + var onCompletedRan = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + mrvts.OnCompleted(s => ((TaskCompletionSource)s).SetResult(true), onCompletedRan, 2, ValueTaskSourceOnCompletedFlags.None); + + Assert.False(onCompletedRan.Task.IsCompleted); + await Task.Delay(1); + Assert.False(onCompletedRan.Task.IsCompleted); + + mrvts.SetResult(42); + Assert.Equal(ValueTaskSourceStatus.Succeeded, mrvts.GetStatus(2)); + Assert.Equal(42, mrvts.GetResult(2)); + + await onCompletedRan.Task; + + Assert.Equal(2, mrvts.Version); + } + + [Fact] + public void SetException_BeforeOnCompleted_ResultAvailableSynchronously() + { + var mrvts = new ManualResetValueTaskSource(); + mrvts.Reset(); + mrvts.Reset(); + Assert.Equal(2, mrvts.Version); + + var e = new FormatException(); + mrvts.SetException(e); + + Assert.Equal(ValueTaskSourceStatus.Faulted, mrvts.GetStatus(2)); + Assert.Same(e, Assert.Throws(() => mrvts.GetResult(2))); + + var mres = new ManualResetEventSlim(); + mrvts.OnCompleted(s => ((ManualResetEventSlim)s).Set(), mres, 2, ValueTaskSourceOnCompletedFlags.None); + mres.Wait(); + + Assert.Equal(2, mrvts.Version); + } + + [Fact] + public async Task SetException_AfterOnCompleted_ResultAvailableAsynchronously() + { + var mrvts = new ManualResetValueTaskSource(); + mrvts.Reset(); + mrvts.Reset(); + Assert.Equal(2, mrvts.Version); + + Assert.Equal(ValueTaskSourceStatus.Pending, mrvts.GetStatus(2)); + Assert.Throws(() => mrvts.GetResult(2)); + + var onCompletedRan = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + mrvts.OnCompleted(s => ((TaskCompletionSource)s).SetResult(true), onCompletedRan, 2, ValueTaskSourceOnCompletedFlags.None); + + Assert.False(onCompletedRan.Task.IsCompleted); + await Task.Delay(1); + Assert.False(onCompletedRan.Task.IsCompleted); + + var e = new FormatException(); + mrvts.SetException(e); + + Assert.Equal(ValueTaskSourceStatus.Faulted, mrvts.GetStatus(2)); + Assert.Same(e, Assert.Throws(() => mrvts.GetResult(2))); + + await onCompletedRan.Task; + + Assert.Equal(2, mrvts.Version); + } + + [Fact] + public void SetException_OperationCanceledException_StatusIsCanceled() + { + var mrvts = new ManualResetValueTaskSource(); + var e = new OperationCanceledException(); + mrvts.SetException(e); + Assert.Equal(ValueTaskSourceStatus.Canceled, mrvts.GetStatus(0)); + Assert.Same(e, Assert.Throws(() => mrvts.GetResult(0))); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void FlowContext_SetBeforeOnCompleted_FlowsIfExpected(bool flowContext) + { + var mres = new ManualResetEventSlim(); + var mrvts = new ManualResetValueTaskSource(); + + mrvts.RunContinuationsAsynchronously = true; + + mrvts.SetResult(1); + + var al = new AsyncLocal(); + al.Value = 42; + mrvts.OnCompleted( + _ => { Assert.Equal(flowContext ? 42 : 0, al.Value); mres.Set(); }, + null, + 0, + flowContext ? ValueTaskSourceOnCompletedFlags.FlowExecutionContext : ValueTaskSourceOnCompletedFlags.None); + + mres.Wait(); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void FlowContext_SetAfterOnCompleted_FlowsIfExpected(bool flowContext) + { + var mres = new ManualResetEventSlim(); + var mrvts = new ManualResetValueTaskSource(); + + mrvts.RunContinuationsAsynchronously = true; + + var al = new AsyncLocal(); + al.Value = 42; + mrvts.OnCompleted( + _ => { Assert.Equal(flowContext ? 42 : 0, al.Value); mres.Set(); }, + null, + 0, + flowContext ? ValueTaskSourceOnCompletedFlags.FlowExecutionContext : ValueTaskSourceOnCompletedFlags.None); + + mrvts.SetResult(1); + + mres.Wait(); + } + + [Fact] + public void OnCompleted_NullDelegate_Throws() + { + var mrvts = new ManualResetValueTaskSource(); + AssertExtensions.Throws("continuation", () => mrvts.OnCompleted(null, new object(), 0, ValueTaskSourceOnCompletedFlags.None)); + } + + [Fact] + public void OnCompleted_UsedTwiceBeforeCompletion_Throws() + { + var mrvts = new ManualResetValueTaskSource(); + mrvts.OnCompleted(_ => { }, null, 0, ValueTaskSourceOnCompletedFlags.None); + Assert.Throws(() => mrvts.OnCompleted(_ => { }, null, 0, ValueTaskSourceOnCompletedFlags.None)); + } + + [Fact] + public void OnCompleted_UnknownFlagsIgnored() + { + var mrvts = new ManualResetValueTaskSource(); + mrvts.OnCompleted(_ => { }, new object(), 0, (ValueTaskSourceOnCompletedFlags)int.MaxValue); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void OnCompleted_ContinuationAlwaysInvokedAsynchronously(bool runContinuationsAsynchronously) + { + var mres = new ManualResetEventSlim(); + var mrvts = new ManualResetValueTaskSource() { RunContinuationsAsynchronously = runContinuationsAsynchronously }; + for (short i = 0; i < 10; i++) + { + int threadId = Environment.CurrentManagedThreadId; + mrvts.SetResult(42); + mrvts.OnCompleted( + _ => + { + Assert.NotEqual(threadId, Environment.CurrentManagedThreadId); + mres.Set(); + }, + null, + i, + ValueTaskSourceOnCompletedFlags.None); + mrvts.Reset(); + mres.Reset(); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SetResult_RunContinuationsAsynchronously_ContinuationInvokedAccordingly(bool runContinuationsAsynchronously) + { + var mres = new ManualResetEventSlim(); + var mrvts = new ManualResetValueTaskSource() { RunContinuationsAsynchronously = runContinuationsAsynchronously }; + for (short i = 0; i < 10; i++) + { + int threadId = Environment.CurrentManagedThreadId; + mrvts.OnCompleted( + _ => + { + Assert.Equal(!runContinuationsAsynchronously, threadId == Environment.CurrentManagedThreadId); + mres.Set(); + }, + null, + i, + ValueTaskSourceOnCompletedFlags.None); + mrvts.SetResult(42); + mrvts.Reset(); + mres.Reset(); + } + } + + [Theory] + [InlineData(false, false, false)] + [InlineData(false, false, true)] + [InlineData(false, true, false)] + [InlineData(false, true, true)] + [InlineData(true, false, false)] + [InlineData(true, false, true)] + [InlineData(true, true, false)] + [InlineData(true, true, true)] + public async Task SynchronizationContext_CaptureIfRequested( + bool runContinuationsAsynchronously, bool captureSyncCtx, bool setBeforeOnCompleted) + { + await Task.Run(async () => // escape xunit sync ctx + { + var mrvts = new ManualResetValueTaskSource() { RunContinuationsAsynchronously = runContinuationsAsynchronously }; + + if (setBeforeOnCompleted) + { + mrvts.SetResult(42); + } + + var tcs = new TaskCompletionSource(); + var sc = new TrackingSynchronizationContext(); + SynchronizationContext.SetSynchronizationContext(sc); + Assert.Equal(0, sc.Posts); + mrvts.OnCompleted( + _ => tcs.SetResult(true), + null, + 0, + captureSyncCtx ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None); + SynchronizationContext.SetSynchronizationContext(null); + + if (!setBeforeOnCompleted) + { + mrvts.SetResult(42); + } + + await tcs.Task; + Assert.Equal(captureSyncCtx ? 1 : 0, sc.Posts); + }); + } + + [Theory] + [InlineData(false, false, false)] + [InlineData(false, false, true)] + [InlineData(false, true, false)] + [InlineData(false, true, true)] + [InlineData(true, false, false)] + [InlineData(true, false, true)] + [InlineData(true, true, false)] + [InlineData(true, true, true)] + public async Task TaskScheduler_CaptureIfRequested( + bool runContinuationsAsynchronously, bool captureTaskScheduler, bool setBeforeOnCompleted) + { + await Task.Run(async () => // escape xunit sync ctx + { + var mrvts = new ManualResetValueTaskSource() { RunContinuationsAsynchronously = runContinuationsAsynchronously }; + + if (setBeforeOnCompleted) + { + mrvts.SetResult(42); + } + + var tcs = new TaskCompletionSource(); + var ts = new TrackingTaskScheduler(); + Assert.Equal(0, ts.QueueTasks); + await Task.Factory.StartNew(() => + { + mrvts.OnCompleted( + _ => tcs.SetResult(true), + null, + 0, + captureTaskScheduler ? ValueTaskSourceOnCompletedFlags.UseSchedulingContext : ValueTaskSourceOnCompletedFlags.None); + }, CancellationToken.None, TaskCreationOptions.None, ts); + + if (!setBeforeOnCompleted) + { + mrvts.SetResult(42); + } + + await tcs.Task; + Assert.Equal(captureTaskScheduler ? 2 : 1, ts.QueueTasks); + }); + } + + private sealed class TrackingSynchronizationContext : SynchronizationContext + { + public int Posts; + + public override void Post(SendOrPostCallback d, object state) + { + Interlocked.Increment(ref Posts); + base.Post(d, state); + } + } + + private sealed class TrackingTaskScheduler : TaskScheduler + { + public int QueueTasks; + + protected override void QueueTask(Task task) + { + QueueTasks++; + ThreadPool.QueueUserWorkItem(_ => TryExecuteTask(task)); + } + + protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) => false; + protected override IEnumerable GetScheduledTasks() => null; + } + [Fact] public async Task AsyncEnumerable_Success() { @@ -76,34 +424,6 @@ public async Task AsyncEnumerable_Success() } } - // The following is a sketch of an implementation to explore the IAsyncEnumerable feature. - // This should be replaced with the real implementation when available. - // https://github.com/dotnet/csharplang/issues/43 - - internal interface IAsyncEnumerable - { - IAsyncEnumerator GetAsyncEnumerator(); - } - - internal interface IAsyncEnumerator : IAsyncDisposable - { - // One of two potential shapes for IAsyncEnumerator; another is - // ValueTask WaitForNextAsync(); - // bool TryGetNext(out T current); - // which has several advantages, including that while the next - // result is available synchronously, it incurs only one interface - // call rather than two, and doesn't incur any boilerplate related - // to await. - - ValueTask MoveNextAsync(); - T Current { get; } - } - - internal interface IAsyncDisposable - { - ValueTask DisposeAsync(); - } - // Approximate compiler-generated code for: // internal static AsyncEnumerable CountAsync(int items) // { @@ -120,7 +440,6 @@ private sealed class CountAsyncEnumerable : IAsyncEnumerable, // used as the enumerable itself IAsyncEnumerator, // used as the enumerator returned from first call to enumerable's GetAsyncEnumerator IValueTaskSource, // used as the backing store behind the ValueTask returned from each MoveNextAsync - IStrongBox>, // exposes its ValueTaskSource logic implementation IAsyncStateMachine // uses existing builder's support for ExecutionContext, optimized awaits, etc. { // This implementation will generally incur only two allocations of overhead @@ -139,9 +458,9 @@ private sealed class CountAsyncEnumerable : /// Current state of the state machine. private int _state = StateCtor; /// All of the logic for managing the IValueTaskSource implementation - private ManualResetValueTaskSourceLogic _vts; // mutable struct; do not make this readonly + private ManualResetValueTaskSourceCore _vts; // mutable struct; do not make this readonly /// Builder used for efficiently waiting and appropriately managing ExecutionContext. - private AsyncTaskMethodBuilder _builder = AsyncTaskMethodBuilder.Create(); // mutable struct; do not make this readonly + private AsyncIteratorMethodBuilder _builder = AsyncIteratorMethodBuilder.Create(); // mutable struct; do not make this readonly private readonly int _param_items; @@ -152,11 +471,8 @@ private sealed class CountAsyncEnumerable : public CountAsyncEnumerable(int items) { _local_items = _param_items = items; - _vts = new ManualResetValueTaskSourceLogic(this); } - ref ManualResetValueTaskSourceLogic IStrongBox>.Value => ref _vts; - public IAsyncEnumerator GetAsyncEnumerator() => Interlocked.CompareExchange(ref _state, StateStart, StateCtor) == StateCtor ? this : @@ -167,7 +483,7 @@ public ValueTask MoveNextAsync() _vts.Reset(); CountAsyncEnumerable inst = this; - _builder.Start(ref inst); // invokes MoveNext, protected by ExecutionContext guards + _builder.MoveNext(ref inst); // invokes MoveNext, protected by ExecutionContext guards switch (_vts.GetStatus(_vts.Version)) { @@ -211,6 +527,7 @@ public void MoveNext() goto case 1; } _state = int.MaxValue; + _builder.Complete(); _vts.SetResult(false); return; @@ -235,6 +552,7 @@ public void MoveNext() catch (Exception e) { _state = int.MaxValue; + _builder.Complete(); _vts.SetException(e); // see https://github.com/dotnet/roslyn/issues/26567; we may want to move this out of the catch return; } diff --git a/src/System.Threading.Tasks.Extensions/tests/Performance/Perf.ValueTask.cs b/src/System.Threading.Tasks.Extensions/tests/Performance/Perf.ValueTask.cs index 3a09444b5040..bfb186015322 100644 --- a/src/System.Threading.Tasks.Extensions/tests/Performance/Perf.ValueTask.cs +++ b/src/System.Threading.Tasks.Extensions/tests/Performance/Perf.ValueTask.cs @@ -4,7 +4,7 @@ using System.Runtime.CompilerServices; using System.Threading.Tasks.Sources; -using System.Threading.Tasks.Tests; +using System.Threading.Tasks.Sources.Tests; using Microsoft.Xunit.Performance; using Xunit; diff --git a/src/System.Threading.Tasks.Extensions/tests/System.Threading.Tasks.Extensions.Tests.csproj b/src/System.Threading.Tasks.Extensions/tests/System.Threading.Tasks.Extensions.Tests.csproj index eb9ee578df82..952b18a137c2 100644 --- a/src/System.Threading.Tasks.Extensions/tests/System.Threading.Tasks.Extensions.Tests.csproj +++ b/src/System.Threading.Tasks.Extensions/tests/System.Threading.Tasks.Extensions.Tests.csproj @@ -6,6 +6,8 @@ + + @@ -16,4 +18,4 @@ Common\System\Threading\Tasks\Sources\ManualResetValueTaskSourceFactory.cs - \ No newline at end of file + diff --git a/src/System.Threading.Tasks.Extensions/tests/ValueTaskTests.cs b/src/System.Threading.Tasks.Extensions/tests/ValueTaskTests.cs index 649f5651ac37..b3583d713fbb 100644 --- a/src/System.Threading.Tasks.Extensions/tests/ValueTaskTests.cs +++ b/src/System.Threading.Tasks.Extensions/tests/ValueTaskTests.cs @@ -6,6 +6,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Threading.Tasks.Sources; +using System.Threading.Tasks.Sources.Tests; using Xunit; namespace System.Threading.Tasks.Tests