// 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.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace System.Threading.Tasks { public static class AsyncEnumerableExtensions { #if !BCL_HAS_CONFIGUREAWAIT // https://github.com/dotnet/coreclr/pull/21939 /// Configures how awaits on the tasks returned from an async iteration will be performed. /// The type of the objects being iterated. /// The source enumerable being iterated. /// Whether to capture and marshal back to the current context. /// The configured enumerable. public static ConfiguredCancelableAsyncEnumerable ConfigureAwait( this IAsyncEnumerable source, bool continueOnCapturedContext) => new ConfiguredCancelableAsyncEnumerable(source, continueOnCapturedContext, cancellationToken: default); /// Sets the to be passed to when iterating. /// The type of the objects being iterated. /// The source enumerable being iterated. /// The to use. /// The configured enumerable. public static ConfiguredCancelableAsyncEnumerable WithCancellation( this IAsyncEnumerable source, CancellationToken cancellationToken) => new ConfiguredCancelableAsyncEnumerable(source, continueOnCapturedContext: true, cancellationToken); #endif // // REVIEW: `await using (var e = xs.GetAsyncEnumerator().ConfigureAwait(false)) { ... }` leads to the following error when using BCL types. // // error CS8410: 'ConfiguredCancelableAsyncEnumerable.Enumerator': type used in an async using statement must be implicitly convertible to 'System.IAsyncDisposable' // // See https://github.com/dotnet/csharplang/blob/master/meetings/2019/LDM-2019-01-16.md#pattern-based-disposal-in-await-foreach for the issues with // `await foreach` (but not `await using`). This should be reviewed with the LDM. Also see https://github.com/dotnet/csharplang/issues/1623. // #if BCL_HAS_CONFIGUREAWAIT && AWAIT_USING_REQUIRES_IASYNCDISPOSABLE public static ConfiguredAsyncEnumerator ConfigureAwait(this IAsyncEnumerator enumerator, bool continueOnCapturedContext) { if (enumerator == null) throw Error.ArgumentNull(nameof(enumerator)); return new ConfiguredAsyncEnumerator(enumerator, continueOnCapturedContext); } /// Provides an awaitable async enumerator that enables cancelable iteration and configured awaits. [StructLayout(LayoutKind.Auto)] public readonly struct ConfiguredAsyncEnumerator : IAsyncDisposable { private readonly IAsyncEnumerator _enumerator; private readonly bool _continueOnCapturedContext; internal ConfiguredAsyncEnumerator(IAsyncEnumerator enumerator, bool continueOnCapturedContext) { _enumerator = enumerator; _continueOnCapturedContext = continueOnCapturedContext; } /// Advances the enumerator asynchronously to the next element of the collection. /// /// A that will complete with a result of true /// if the enumerator was successfully advanced to the next element, or false if the enumerator has /// passed the end of the collection. /// public ConfiguredValueTaskAwaitable MoveNextAsync() => _enumerator.MoveNextAsync().ConfigureAwait(_continueOnCapturedContext); /// Gets the element in the collection at the current position of the enumerator. public T Current => _enumerator.Current; /// /// Performs application-defined tasks associated with freeing, releasing, or /// resetting unmanaged resources asynchronously. /// public ConfiguredValueTaskAwaitable DisposeAsync() => _enumerator.DisposeAsync().ConfigureAwait(_continueOnCapturedContext); async ValueTask IAsyncDisposable.DisposeAsync() => await _enumerator.DisposeAsync().ConfigureAwait(_continueOnCapturedContext); } #else public static ConfiguredCancelableAsyncEnumerable.Enumerator ConfigureAwait(this IAsyncEnumerator enumerator, bool continueOnCapturedContext) { if (enumerator == null) throw Error.ArgumentNull(nameof(enumerator)); return new ConfiguredCancelableAsyncEnumerable.Enumerator(enumerator, continueOnCapturedContext); } #endif } }