diff --git a/Package/Core/Cancelations/Internal/CancelationInternal.cs b/Package/Core/Cancelations/Internal/CancelationInternal.cs index 975610799..25e47f374 100644 --- a/Package/Core/Cancelations/Internal/CancelationInternal.cs +++ b/Package/Core/Cancelations/Internal/CancelationInternal.cs @@ -26,7 +26,7 @@ namespace Proto.Promises { - internal static partial class Internal + partial class Internal { #if !PROTO_PROMISE_DEVELOPER_MODE [DebuggerNonUserCode, StackTraceHidden] diff --git a/Package/Core/InternalShared/InterfacesInternal.cs b/Package/Core/InternalShared/InterfacesInternal.cs index 643d12d42..43dabb76e 100644 --- a/Package/Core/InternalShared/InterfacesInternal.cs +++ b/Package/Core/InternalShared/InterfacesInternal.cs @@ -6,7 +6,7 @@ namespace Proto.Promises { - internal static partial class Internal + partial class Internal { internal partial interface ITraceable { } diff --git a/Package/Core/InternalShared/ValueStopwatch.cs b/Package/Core/InternalShared/ValueStopwatch.cs index f3ae3fb93..3cfc002a7 100644 --- a/Package/Core/InternalShared/ValueStopwatch.cs +++ b/Package/Core/InternalShared/ValueStopwatch.cs @@ -18,7 +18,7 @@ namespace Proto.Promises { - internal static partial class Internal + partial class Internal { // Idea from https://www.meziantou.net/how-to-measure-elapsed-time-without-allocating-a-stopwatch.htm #pragma warning disable IDE0250 // Make struct 'readonly' diff --git a/Package/Core/Linq/CompilerServices/AsyncStreamWriter.cs b/Package/Core/Linq/CompilerServices/AsyncStreamWriter.cs index 6aa0d5f84..6b16d6662 100644 --- a/Package/Core/Linq/CompilerServices/AsyncStreamWriter.cs +++ b/Package/Core/Linq/CompilerServices/AsyncStreamWriter.cs @@ -13,11 +13,11 @@ namespace Proto.Promises.Async.CompilerServices #endif public readonly struct AsyncStreamWriter { - private readonly Internal.PromiseRefBase.AsyncEnumerableBase _target; + private readonly Internal.PromiseRefBase.AsyncEnumerableWithIterator _target; private readonly int _id; [MethodImpl(Internal.InlineOption)] - internal AsyncStreamWriter(Internal.PromiseRefBase.AsyncEnumerableBase target, int id) + internal AsyncStreamWriter(Internal.PromiseRefBase.AsyncEnumerableWithIterator target, int id) { _target = target; _id = id; @@ -41,11 +41,11 @@ public AsyncStreamYielder YieldAsync(T value) #endif public readonly partial struct AsyncStreamYielder : ICriticalNotifyCompletion, Internal.IPromiseAwaiter { - private readonly Internal.PromiseRefBase.AsyncEnumerableBase _target; + private readonly Internal.PromiseRefBase.AsyncEnumerableWithIterator _target; private readonly int _enumerableId; [MethodImpl(Internal.InlineOption)] - internal AsyncStreamYielder(Internal.PromiseRefBase.AsyncEnumerableBase target, int enumerableId) + internal AsyncStreamYielder(Internal.PromiseRefBase.AsyncEnumerableWithIterator target, int enumerableId) { _target = target; _enumerableId = enumerableId; diff --git a/Package/Core/Linq/Generators/Canceled.cs b/Package/Core/Linq/Generators/Canceled.cs index dfa41371b..cf18215b3 100644 --- a/Package/Core/Linq/Generators/Canceled.cs +++ b/Package/Core/Linq/Generators/Canceled.cs @@ -12,7 +12,7 @@ namespace Proto.Promises.Linq { #if CSHARP_7_3_OR_NEWER // We only expose AsyncEnumerable where custom async method builders are supported. - public static partial class AsyncEnumerable + partial class AsyncEnumerable { /// /// Generates an async-enumerable sequence that will be immediately canceled when iterated. @@ -72,8 +72,6 @@ internal override Promise DisposeAsync(int id) // Do nothing, just return a resolved promise. => Promise.Resolved(); - protected override void Start(int enumerableId) { throw new System.InvalidOperationException(); } - protected override void DisposeAndReturnToPool() { throw new System.InvalidOperationException(); } internal override void MaybeDispose() { throw new System.InvalidOperationException(); } } } diff --git a/Package/Core/Linq/Generators/Create.cs b/Package/Core/Linq/Generators/Create.cs index 440be4dee..89104bf66 100644 --- a/Package/Core/Linq/Generators/Create.cs +++ b/Package/Core/Linq/Generators/Create.cs @@ -43,7 +43,7 @@ public static AsyncEnumerable Create(Func, CancelationTo { ValidateArgument(asyncIterator, nameof(asyncIterator), 1); - var enumerable = Internal.AsyncEnumerableImpl>.GetOrCreate(new Internal.AsyncIterator(asyncIterator)); + var enumerable = Internal.AsyncEnumerableCreate>.GetOrCreate(new Internal.AsyncIterator(asyncIterator)); return new AsyncEnumerable(enumerable); } @@ -54,7 +54,7 @@ public static AsyncEnumerable Create(TCapture captureValue, Func>.GetOrCreate(new Internal.AsyncIterator(captureValue, asyncIterator)); + var enumerable = Internal.AsyncEnumerableCreate>.GetOrCreate(new Internal.AsyncIterator(captureValue, asyncIterator)); return new AsyncEnumerable(enumerable); } } diff --git a/Package/Core/Linq/Generators/Empty.cs b/Package/Core/Linq/Generators/Empty.cs index 8badef740..0b92890ae 100644 --- a/Package/Core/Linq/Generators/Empty.cs +++ b/Package/Core/Linq/Generators/Empty.cs @@ -10,7 +10,7 @@ namespace Proto.Promises.Linq { #if CSHARP_7_3_OR_NEWER // We only expose AsyncEnumerable where custom async method builders are supported. - public static partial class AsyncEnumerable + partial class AsyncEnumerable { /// /// Returns an empty async-enumerable sequence. @@ -21,7 +21,7 @@ public static AsyncEnumerable Empty() => AsyncEnumerable.Empty(); } - public readonly partial struct AsyncEnumerable + partial struct AsyncEnumerable { /// /// Returns an empty async-enumerable sequence. @@ -69,8 +69,6 @@ internal override Promise DisposeAsync(int id) // Do nothing, just return a resolved promise. => Promise.Resolved(); - protected override void Start(int enumerableId) { throw new System.InvalidOperationException(); } - protected override void DisposeAndReturnToPool() { throw new System.InvalidOperationException(); } internal override void MaybeDispose() { throw new System.InvalidOperationException(); } } } diff --git a/Package/Core/Linq/Generators/Range.cs b/Package/Core/Linq/Generators/Range.cs index d459ac368..fc5094ecb 100644 --- a/Package/Core/Linq/Generators/Range.cs +++ b/Package/Core/Linq/Generators/Range.cs @@ -12,7 +12,7 @@ namespace Proto.Promises.Linq { #if CSHARP_7_3_OR_NEWER // We only expose AsyncEnumerable where custom async method builders are supported. - public static partial class AsyncEnumerable + partial class AsyncEnumerable { /// /// Generates an async-enumerable sequence of integral numbers within a specified range. @@ -107,20 +107,19 @@ internal override Promise DisposeAsync(int id) #if PROMISE_DEBUG || PROTO_PROMISE_DEVELOPER_MODE SetCompletionState(null, Promise.State.Resolved); #endif - DisposeAndReturnToPool(); + Dispose(); } // IAsyncDisposable.DisposeAsync must not throw if it's called multiple times, according to MSDN documentation. return Promise.Resolved(); } - protected override void DisposeAndReturnToPool() + new private void Dispose() { - Dispose(); + base.Dispose(); _disposed = true; ObjectPool.MaybeRepool(this); } - protected override void Start(int enumerableId) { throw new System.InvalidOperationException(); } internal override void MaybeDispose() { throw new System.InvalidOperationException(); } } } diff --git a/Package/Core/Linq/Generators/Rejected.cs b/Package/Core/Linq/Generators/Rejected.cs index 929cebe8f..fdf83f786 100644 --- a/Package/Core/Linq/Generators/Rejected.cs +++ b/Package/Core/Linq/Generators/Rejected.cs @@ -12,7 +12,7 @@ namespace Proto.Promises.Linq { #if CSHARP_7_3_OR_NEWER // We only expose AsyncEnumerable where custom async method builders are supported. - public static partial class AsyncEnumerable + partial class AsyncEnumerable { /// /// Generates an async-enumerable sequence that will be immediately rejected with the provided reason when iterated. diff --git a/Package/Core/Linq/Generators/Repeat.cs b/Package/Core/Linq/Generators/Repeat.cs index 27feaeee6..0123216e0 100644 --- a/Package/Core/Linq/Generators/Repeat.cs +++ b/Package/Core/Linq/Generators/Repeat.cs @@ -12,7 +12,7 @@ namespace Proto.Promises.Linq { #if CSHARP_7_3_OR_NEWER // We only expose AsyncEnumerable where custom async method builders are supported. - public static partial class AsyncEnumerable + partial class AsyncEnumerable { /// /// Generates an async-enumerable sequence that contains one repeated value. @@ -110,21 +110,20 @@ internal override Promise DisposeAsync(int id) #if PROMISE_DEBUG || PROTO_PROMISE_DEVELOPER_MODE SetCompletionState(null, Promise.State.Resolved); #endif - DisposeAndReturnToPool(); + Dispose(); } // IAsyncDisposable.DisposeAsync must not throw if it's called multiple times, according to MSDN documentation. return Promise.Resolved(); } - protected override void DisposeAndReturnToPool() + new private void Dispose() { - Dispose(); + base.Dispose(); _current = default; _disposed = true; ObjectPool.MaybeRepool(this); } - protected override void Start(int enumerableId) { throw new System.InvalidOperationException(); } internal override void MaybeDispose() { throw new System.InvalidOperationException(); } } } diff --git a/Package/Core/Linq/Generators/Return.cs b/Package/Core/Linq/Generators/Return.cs index b0dda5e97..479d6e15d 100644 --- a/Package/Core/Linq/Generators/Return.cs +++ b/Package/Core/Linq/Generators/Return.cs @@ -12,7 +12,7 @@ namespace Proto.Promises.Linq { #if CSHARP_7_3_OR_NEWER // We only expose AsyncEnumerable where custom async method builders are supported. - public static partial class AsyncEnumerable + partial class AsyncEnumerable { /// /// Generates an async-enumerable sequence that contains a single element. @@ -86,21 +86,20 @@ internal override Promise DisposeAsync(int id) #if PROMISE_DEBUG || PROTO_PROMISE_DEVELOPER_MODE SetCompletionState(null, Promise.State.Resolved); #endif - DisposeAndReturnToPool(); + Dispose(); } // IAsyncDisposable.DisposeAsync must not throw if it's called multiple times, according to MSDN documentation. return Promise.Resolved(); } - protected override void DisposeAndReturnToPool() + new private void Dispose() { - Dispose(); + base.Dispose(); _current = default; _disposed = true; ObjectPool.MaybeRepool(this); } - protected override void Start(int enumerableId) { throw new System.InvalidOperationException(); } internal override void MaybeDispose() { throw new System.InvalidOperationException(); } } } diff --git a/Package/Core/Linq/Internal/AsyncEnumerableInternal.cs b/Package/Core/Linq/Internal/AsyncEnumerableInternal.cs index 5f056fd3a..49c8454be 100644 --- a/Package/Core/Linq/Internal/AsyncEnumerableInternal.cs +++ b/Package/Core/Linq/Internal/AsyncEnumerableInternal.cs @@ -76,17 +76,11 @@ partial class PromiseRefBase #endif internal abstract class AsyncEnumerableBase : PromiseSingleAwait { - // This is used as the backing reference to 3 different awaiters. MoveNextAsync (Promise), DisposeAsync (Promise), and YieldAsync (AsyncStreamYielder). - // We use `Interlocked.CompareExchange(ref _enumerableId` to enforce only 1 awaiter uses it at a time, in the correct order. - // We use a separate field for AsyncStreamYielder continuation, because using _next for 2 separate async functions (the iterator and the consumer) proves problematic. - protected PromiseRefBase _iteratorPromiseRef; + internal CancelationToken _cancelationToken; protected T _current; - private int _iteratorCompleteExpectedId; - private int _iteratorCompleteId; protected int _enumerableId = 1; // Start with Id 1 instead of 0 to reduce risk of false positives. protected bool _disposed; protected bool _isStarted; - internal CancelationToken _cancelationToken; internal int EnumerableId { @@ -149,8 +143,23 @@ internal T GetCurrent(int id) return _current; } - [MethodImpl(InlineOption)] - internal virtual Promise MoveNextAsync(int id) + internal abstract Promise MoveNextAsync(int id); + internal abstract Promise DisposeAsync(int id); + } // class AsyncEnumerableBase + +#if !PROTO_PROMISE_DEVELOPER_MODE + [DebuggerNonUserCode, StackTraceHidden] +#endif + internal abstract class AsyncEnumerableWithIterator : AsyncEnumerableBase + { + // This is used as the backing reference to 3 different awaiters. MoveNextAsync (Promise), DisposeAsync (Promise), and YieldAsync (AsyncStreamYielder). + // We use `Interlocked.CompareExchange(ref _enumerableId` to enforce only 1 awaiter uses it at a time, in the correct order. + // We use a separate field for AsyncStreamYielder continuation, because using _next for 2 separate async functions (the iterator and the consumer) proves problematic. + protected PromiseRefBase _iteratorPromiseRef; + private int _iteratorCompleteExpectedId; + private int _iteratorCompleteId; + + internal override Promise MoveNextAsync(int id) { // We increment by 1 when MoveNextAsync, then decrement by 1 when YieldAsync. int newId = id + 1; @@ -183,6 +192,16 @@ internal virtual Promise MoveNextAsync(int id) return new Promise(this, Id, 0); } + private void MoveNext() + { + // Invalidate the previous awaiter. + IncrementPromiseIdAndClearPrevious(); + // Reset for the next awaiter. + ResetWithoutStacktrace(); + // Handle iterator promise to move the async state machine forward. + InterlockedExchange(ref _iteratorPromiseRef, null).Handle(this, null, Promise.State.Resolved); + } + [MethodImpl(InlineOption)] internal AsyncStreamYielder YieldAsync(in T value, int id) { @@ -199,7 +218,7 @@ internal AsyncStreamYielder YieldAsync(in T value, int id) return new AsyncStreamYielder(this, newId); } - internal virtual Promise DisposeAsync(int id) + internal override Promise DisposeAsync(int id) { int newId = id + 3; // When the async iterator function completes before DisposeAsync is called, it's set to id + 2. @@ -317,43 +336,32 @@ internal void AwaitOnCompletedForAsyncStreamYielder(PromiseRefBase asyncPromiseR HandleNextInternal(null, Promise.State.Resolved); } - protected void MoveNext() - { - // Invalidate the previous awaiter. - IncrementPromiseIdAndClearPrevious(); - // Reset for the next awaiter. - ResetWithoutStacktrace(); - // Handle iterator promise to move the async state machine forward. - InterlockedExchange(ref _iteratorPromiseRef, null).Handle(this, null, Promise.State.Resolved); - } - protected abstract void Start(int enumerableId); - protected abstract void DisposeAndReturnToPool(); - } // class AsyncEnumerableBase + } // class AsyncEnumerableWithIterator } // class PromiseRefBase #if !PROTO_PROMISE_DEVELOPER_MODE [DebuggerNonUserCode, StackTraceHidden] #endif - internal sealed class AsyncEnumerableImpl : PromiseRefBase.AsyncEnumerableBase + internal sealed class AsyncEnumerableCreate : PromiseRefBase.AsyncEnumerableWithIterator where TIterator : IAsyncIterator { private TIterator _iterator; - private AsyncEnumerableImpl() { } + private AsyncEnumerableCreate() { } [MethodImpl(InlineOption)] - private static AsyncEnumerableImpl GetOrCreate() + private static AsyncEnumerableCreate GetOrCreate() { - var obj = ObjectPool.TryTakeOrInvalid>(); + var obj = ObjectPool.TryTakeOrInvalid>(); return obj == InvalidAwaitSentinel.s_instance - ? new AsyncEnumerableImpl() - : obj.UnsafeAs>(); + ? new AsyncEnumerableCreate() + : obj.UnsafeAs>(); } [MethodImpl(InlineOption)] - internal static AsyncEnumerableImpl GetOrCreate(in TIterator iterator) + internal static AsyncEnumerableCreate GetOrCreate(in TIterator iterator) { var enumerable = GetOrCreate(); enumerable.Reset(); diff --git a/Package/Core/Linq/Internal/MergeInternal.cs b/Package/Core/Linq/Internal/MergeInternal.cs index 7c2b9b823..7a2796d8c 100644 --- a/Package/Core/Linq/Internal/MergeInternal.cs +++ b/Package/Core/Linq/Internal/MergeInternal.cs @@ -22,7 +22,7 @@ partial class Internal #if !PROTO_PROMISE_DEVELOPER_MODE [DebuggerNonUserCode, StackTraceHidden] #endif - internal abstract class AsyncEnumerableMergerBase : PromiseRefBase.AsyncEnumerableBase + internal abstract class AsyncEnumerableMergerBase : PromiseRefBase.AsyncEnumerableWithIterator { // TODO: optimize these collections. protected readonly List> _enumerators = new List>(); diff --git a/Package/Core/Promises/Internal/RejectContainersInternal.cs b/Package/Core/Promises/Internal/RejectContainersInternal.cs index 6b325d61a..d568f6ccb 100644 --- a/Package/Core/Promises/Internal/RejectContainersInternal.cs +++ b/Package/Core/Promises/Internal/RejectContainersInternal.cs @@ -22,7 +22,7 @@ namespace Proto.Promises { - internal static partial class Internal + partial class Internal { // Extension method instead of including on the interface, since old IL2CPP compiler does not support virtual generics with structs. internal static bool TryGetValue(this IRejectContainer rejectContainer, out TValue converted)