// 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
}
}