< Summary

Information
Class: AmbientServices.IAsyncEnumerableExtensions<T>
Assembly: AmbientServices.Async
File(s): /home/runner/work/AmbientServices.Async/AmbientServices.Async/AmbientServices.Async/Async.cs
Tag: 76_25271648613
Line coverage
100%
Covered lines: 2
Uncovered lines: 0
Coverable lines: 2
Total lines: 706
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
ToSingleItemAsyncEnumerable()100%11100%

File(s)

/home/runner/work/AmbientServices.Async/AmbientServices.Async/AmbientServices.Async/Async.cs

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Diagnostics;
 4using System.Linq;
 5using System.Runtime.CompilerServices;
 6using System.Text;
 7using System.Threading;
 8using System.Threading.Tasks;
 9using System.Xml.Linq;
 10
 11namespace AmbientServices
 12{
 13    /// <summary>
 14    /// A static class to hold utility functions for calling async functions in a non-async context or vice versa, espec
 15    /// When migrating code from sync to async, begin from the bottom off the call stack.
 16    /// Use <see cref="RunSync(Func{ValueTask})"/> or <see cref="RunTaskSync(Func{Task})"/> at the transition from sync 
 17    /// Use await <see cref="Run(Func{ValueTask})"/> or <see cref="RunTask(Func{Task})"/> as the default asynchronous in
 18    /// Use await <see cref="RunAsync(Func{ValueTask},SynchronizationContext?)"/> or <see cref="RunTaskAsync(Func{Task},
 19    /// As migration progresses, calls to <see cref="RunSync(Func{ValueTask})"/> and <see cref="RunTaskSync(Func{Task})"
 20    /// Calls that use await without one of these as the target will run asynchonously in a newly spawned async ambient 
 21    /// </summary>
 22    public static class Async
 23    {
 24        private static SynchronizationContext sDefaultAsyncContext = new();
 25
 26        /// <summary>
 27        /// Gets or sets the default async context, which will be used when forcing tasks to the async context from with
 28        /// </summary>
 29        public static SynchronizationContext DefaultAsyncContext
 30        {
 31            get { return sDefaultAsyncContext; }
 32            set { Interlocked.Exchange(ref sDefaultAsyncContext, value); }
 33        }
 34        /// <summary>
 35        /// Gets the single threaded context to use for spawning tasks to be run on the current thread.
 36        /// </summary>
 37        public static System.Threading.SynchronizationContext SinglethreadedContext => SynchronousSynchronizationContext
 38
 39        /// <summary>
 40        /// Gets whether or not the current context is using synchronous execution.
 41        /// </summary>
 42        public static bool UsingSynchronousExecution => SynchronizationContext.Current == SynchronousSynchronizationCont
 43
 44        private static void RunIfNeeded(Task task)
 45        {
 46            if (task.Status == TaskStatus.Created)
 47            {
 48                task.RunSynchronously(SynchronousTaskScheduler.Default);
 49            }
 50        }
 51        /// <summary>
 52        /// Converts the specified <see cref="AggregateException"/> into the inner exception type if there is only one i
 53        /// If there is more than one inner exception, just returns.
 54        /// </summary>
 55        /// <param name="ex">The <see cref="AggregateException"/></param>
 56        /// <exception cref="ArgumentNullException">If <paramref name="ex"/> is null.</exception>
 57        public static void ConvertAggregateException(AggregateException ex)
 58        {
 59#if NET6_0_OR_GREATER
 60            ArgumentNullException.ThrowIfNull(ex);
 61#else
 62            if (ex == null) throw new ArgumentNullException(nameof(ex));
 63#endif
 64            if (ex.InnerExceptions.Count < 2)
 65            {
 66                System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex.InnerExceptions[0]).Throw();
 67            }
 68        }
 69
 70        private static void WaitAndUnwrapException(this Task t, bool continueOnCapturedContext)
 71        {
 72            try
 73            {
 74                t.ConfigureAwait(continueOnCapturedContext).GetAwaiter().GetResult();
 75            }
 76            catch (AggregateException ex)
 77            {
 78                ConvertAggregateException(ex);
 79                throw;
 80            }
 81        }
 82        private static T WaitAndUnwrapException<T>(this Task<T> t, bool continueOnCapturedContext)
 83        {
 84            try
 85            {
 86                return t.ConfigureAwait(continueOnCapturedContext).GetAwaiter().GetResult();
 87            }
 88            catch (AggregateException ex)
 89            {
 90                ConvertAggregateException(ex);
 91                throw;
 92            }
 93        }
 94        // NOTE that the following are not currently needed because we can't force a ValueTask to run synchronously, and
 95        //        private static void WaitAndUnwrapException(this ValueTask t, bool continueOnCapturedContext)
 96        //        private static T WaitAndUnwrapException<T>(this ValueTask<T> t, bool continueOnCapturedContext)
 97
 98        private static void RunInTemporaryContextWithExceptionConversion(SynchronizationContext newContext, Action a)
 99        {
 100            System.Threading.SynchronizationContext? oldContext = SynchronizationContext.Current;
 101            try
 102            {
 103                SynchronizationContext.SetSynchronizationContext(newContext);
 104                a();
 105            }
 106            catch (AggregateException ex)
 107            {
 108                ConvertAggregateException(ex);
 109                throw;
 110            }
 111            finally
 112            {
 113                SynchronizationContext.SetSynchronizationContext(oldContext);
 114            }
 115        }
 116        private static T RunInTemporaryContextWithExceptionConversion<T>(SynchronizationContext newContext, Func<T> a)
 117        {
 118            System.Threading.SynchronizationContext? oldContext = SynchronizationContext.Current;
 119            try
 120            {
 121                SynchronizationContext.SetSynchronizationContext(newContext);
 122                return a();
 123            }
 124            catch (AggregateException ex)
 125            {
 126                ConvertAggregateException(ex);
 127                throw;
 128            }
 129            finally
 130            {
 131                SynchronizationContext.SetSynchronizationContext(oldContext);
 132            }
 133        }
 134
 135        /// <summary>
 136        /// Runs the specified asynchronous action using the currently ambient mode.
 137        /// </summary>
 138        /// <param name="a">The asynchronous action to run.</param>
 139        [DebuggerStepThrough]
 140        public static Task RunTask(Func<Task> a)
 141        {
 142#if NET6_0_OR_GREATER
 143            ArgumentNullException.ThrowIfNull(a);
 144#else
 145            if (a is null) throw new ArgumentNullException(nameof(a));
 146#endif
 147            if (SynchronizationContext.Current == SynchronousSynchronizationContext.Default)
 148            {
 149                RunTaskSync(a);
 150                return Task.CompletedTask;
 151            }
 152            else
 153            {
 154                return a();
 155            }
 156        }
 157        /// <summary>
 158        /// Runs the specified asynchronous action using the currently ambient mode.
 159        /// </summary>
 160        /// <param name="a">The cancelable asynchronous action to run.</param>
 161        [DebuggerStepThrough]
 162        public static Task<T> RunTask<T>(Func<Task<T>> a)
 163        {
 164#if NET6_0_OR_GREATER
 165            ArgumentNullException.ThrowIfNull(a);
 166#else
 167            if (a is null) throw new ArgumentNullException(nameof(a));
 168#endif
 169            if (SynchronizationContext.Current == SynchronousSynchronizationContext.Default)
 170            {
 171                T t = RunTaskSync(a);
 172                return Task.FromResult(t);
 173            }
 174            else
 175            {
 176                return a();
 177            }
 178        }
 179        /// <summary>
 180        /// Runs the specified asynchronous action using the currently ambient mode.
 181        /// </summary>
 182        /// <param name="a">The cancelable asynchronous action to run.</param>
 183        [DebuggerStepThrough]
 184        public static ValueTask Run(Func<ValueTask> a)
 185        {
 186#if NET6_0_OR_GREATER
 187            ArgumentNullException.ThrowIfNull(a);
 188#else
 189            if (a is null) throw new ArgumentNullException(nameof(a));
 190#endif
 191            if (SynchronizationContext.Current == SynchronousSynchronizationContext.Default)
 192            {
 193                RunSync(a);
 194                return default;
 195            }
 196            else
 197            {
 198                return a();
 199            }
 200        }
 201        /// <summary>
 202        /// Runs the specified asynchronous action using the currently ambient mode.
 203        /// </summary>
 204        /// <param name="a">The cancelable asynchronous action to run.</param>
 205        [DebuggerStepThrough]
 206        public static ValueTask<T> Run<T>(Func<ValueTask<T>> a)
 207        {
 208#if NET6_0_OR_GREATER
 209            ArgumentNullException.ThrowIfNull(a);
 210#else
 211            if (a is null) throw new ArgumentNullException(nameof(a));
 212#endif
 213            if (SynchronizationContext.Current == SynchronousSynchronizationContext.Default)
 214            {
 215                T t = RunSync(a);
 216                return new ValueTask<T>(t);
 217            }
 218            else
 219            {
 220                return a();
 221            }
 222        }
 223        /// <summary>
 224        /// Runs the specified asynchronous action asynchronously and switches the ambient synchronization context to th
 225        /// Use this to run the action in an asynchronous ambient context, but wait on the current thread for it to fini
 226        /// </summary>
 227        /// <param name="a">The asynchronous action to run.</param>
 228        /// <param name="context">The <see cref="SynchronizationContext"/> to use to asynchronously run the task, or nul
 229        [DebuggerStepThrough]
 230        public static Task RunTaskAsync(Func<Task> a, SynchronizationContext? context = null)
 231        {
 232            return RunInTemporaryContextWithExceptionConversion(context ?? sDefaultAsyncContext, () =>
 233            {
 234                Task task = Task.Run(a);       // this should run the task on another thread and should be started using
 235                task.WaitAndUnwrapException(false);
 236                return task;
 237            });
 238        }
 239        /// <summary>
 240        /// Runs the specified asynchronous action asynchronously and switches the ambient synchronization context to th
 241        /// Use this to run the action in an asynchronous ambient context, but wait on the current thread for it to fini
 242        /// </summary>
 243        /// <param name="a">The cancelable asynchronous action to run.</param>
 244        /// <param name="context">The <see cref="SynchronizationContext"/> to use to asynchronously run the task, or nul
 245        [DebuggerStepThrough]
 246        public static Task<T> RunTaskAsync<T>(Func<Task<T>> a, SynchronizationContext? context = null)
 247        {
 248            return RunInTemporaryContextWithExceptionConversion(context ?? sDefaultAsyncContext, () =>
 249            {
 250                Task<T> task = Task.Run(a);       // this should run the task on another thread and should be started us
 251                task.WaitAndUnwrapException(false);
 252                return task;
 253            });
 254        }
 255        /// <summary>
 256        /// Runs the specified asynchronous action asynchronously and switches the ambient synchronization context to th
 257        /// Use this to run the action in an asynchronous ambient context, but wait on the current thread for it to fini
 258        /// </summary>
 259        /// <param name="a">The cancelable asynchronous action to run.</param>
 260        /// <param name="context">The <see cref="SynchronizationContext"/> to use to asynchronously run the task, or nul
 261        [DebuggerStepThrough]
 262        public static ValueTask RunAsync(Func<ValueTask> a, SynchronizationContext? context = null)
 263        {
 264            return RunInTemporaryContextWithExceptionConversion(context ?? sDefaultAsyncContext, () =>
 265            {
 266                Task task = Task.Run(() => a().AsTask());           // I'm not seeing a way to do this without this conv
 267                task.WaitAndUnwrapException(false);
 268                return new ValueTask();
 269            });
 270        }
 271        /// <summary>
 272        /// Runs the specified asynchronous action asynchronously and switches the ambient synchronization context to th
 273        /// Use this to run the action in an asynchronous ambient context, but wait on the current thread for it to fini
 274        /// </summary>
 275        /// <param name="a">The cancelable asynchronous action to run.</param>
 276        /// <param name="context">The <see cref="SynchronizationContext"/> to use to asynchronously run the task, or nul
 277        [DebuggerStepThrough]
 278        public static ValueTask<T> RunAsync<T>(Func<ValueTask<T>> a, SynchronizationContext? context = null)
 279        {
 280            return RunInTemporaryContextWithExceptionConversion(context ?? sDefaultAsyncContext, () =>
 281            {
 282                Task<T> task = Task.Run(() => a().AsTask());        // I'm not seeing a way to do this without this conv
 283                T result = task.WaitAndUnwrapException(false);
 284                return new ValueTask<T>(result);
 285            });
 286        }
 287
 288        /// <summary>
 289        /// Runs the specified asynchronous action synchronously on the current thread, staying on the current thread.
 290        /// </summary>
 291        /// <param name="a">The action to run.</param>
 292        [DebuggerStepThrough]
 293        public static void RunTaskSync(Func<Task> a)
 294        {
 295            RunInTemporaryContextWithExceptionConversion(SynchronousSynchronizationContext.Default, () =>
 296            {
 297                using Task task = a();
 298                RunIfNeeded(task);
 299                task.WaitAndUnwrapException(true);
 300            });
 301        }
 302        /// <summary>
 303        /// Runs the specified asynchronous action synchronously on the current thread, staying on the current thread.
 304        /// </summary>
 305        /// <param name="a">The cancelable asynchronous action to run.</param>
 306        [DebuggerStepThrough]
 307        public static T RunTaskSync<T>(Func<Task<T>> a)
 308        {
 309            return RunInTemporaryContextWithExceptionConversion(SynchronousSynchronizationContext.Default, () =>
 310            {
 311                using Task<T> task = a();
 312                RunIfNeeded(task);
 313                return task.WaitAndUnwrapException(true);
 314            });
 315        }
 316
 317
 318        /// <summary>
 319        /// Runs the specified asynchronous action synchronously on the current thread, staying on the current thread.
 320        /// </summary>
 321        /// <param name="a">The cancelable asynchronous action to run.</param>
 322        [DebuggerStepThrough]
 323        public static T RunSync<T>(Func<ValueTask<T>> a)
 324        {
 325            return RunInTemporaryContextWithExceptionConversion(SynchronousSynchronizationContext.Default, () =>
 326            {
 327                ValueTask<T> valueTask = a();
 328                Task<T> task = valueTask.AsTask();          // I'm not seeing a way to do this without this conversion (
 329                RunIfNeeded(task);
 330                return task.WaitAndUnwrapException(true);
 331            });
 332        }
 333
 334
 335        /// <summary>
 336        /// Runs the specified asynchronous action synchronously on the current thread, staying on the current thread.
 337        /// </summary>
 338        /// <param name="a">The cancelable asynchronous action to run.</param>
 339        [DebuggerStepThrough]
 340        public static void RunSync(Func<ValueTask> a)
 341        {
 342            RunInTemporaryContextWithExceptionConversion(SynchronousSynchronizationContext.Default, () =>
 343            {
 344                ValueTask valueTask = a();
 345                Task task = valueTask.AsTask();          // I'm not seeing a way to do this without this conversion (whi
 346                RunIfNeeded(task);
 347                task.WaitAndUnwrapException(true);
 348            });
 349        }
 350#if NETSTANDARD2_1 || NETCOREAPP3_1_OR_GREATER || NET5_0_OR_GREATER
 351        /// <summary>
 352        /// Synchronously converts an <see cref="IAsyncEnumerable{T}"/> into an <see cref="IEnumerable{T}"/>.
 353        /// Works with infinite collections.
 354        /// </summary>
 355        /// <typeparam name="T">The type being enumerated.</typeparam>
 356        /// <param name="funcAsyncEnumerable">A delegate that returns an <see cref="IAsyncEnumerable{T}"/></param>
 357        /// <param name="cancel">A <see cref="CancellationToken"/> which the caller can use to notify the executor to ca
 358        /// <returns>The <see cref="IEnumerable{T}"/>.</returns>
 359        [DebuggerStepThrough]
 360        public static IEnumerable<T> AsyncEnumerableToEnumerable<T>(Func<IAsyncEnumerable<T>> funcAsyncEnumerable, Cance
 361        {
 362System.Diagnostics.Debug.WriteLine("AsyncEnumerableToEnumerable");
 363#if NET6_0_OR_GREATER
 364            ArgumentNullException.ThrowIfNull(funcAsyncEnumerable);
 365#else
 366            if (funcAsyncEnumerable == null) throw new ArgumentNullException(nameof(funcAsyncEnumerable));
 367#endif
 368System.Diagnostics.Debug.WriteLine("AsyncEnumerableToEnumerable funcAsyncEnumerable not null");
 369            IAsyncEnumerator<T> asyncEnum = funcAsyncEnumerable().GetAsyncEnumerator(cancel);
 370            try
 371            {
 372                while (RunSync(() => asyncEnum.MoveNextAsync()))
 373                {
 374                    cancel.ThrowIfCancellationRequested();
 375                    yield return asyncEnum.Current;
 376                }
 377            }
 378            finally
 379            {
 380                RunSync(() => asyncEnum.DisposeAsync());
 381            }
 382        }
 383        /// <summary>
 384        /// Iterates through the specified async enumerable using the ambient synchronicity and a synchronous delegate.
 385        /// </summary>
 386        /// <typeparam name="T">The type being enumerated.</typeparam>
 387        /// <param name="asyncEnumerable">The <see cref="IAsyncEnumerable{T}"/> to enumerate.</param>
 388        /// <param name="action">The action to perform on each enumerated item.</param>
 389        /// <param name="cancel">A <see cref="CancellationToken"/> that may be used to interrupt the enumeration.</param
 390        /// <returns>A <see cref="ValueTask"/> for the iteration.</returns>
 391        /// <exception cref="ArgumentNullException">If <paramref name="asyncEnumerable"/> or <paramref name="action"/> a
 392        [DebuggerStepThrough]
 393        public static async ValueTask AwaitForEach<T>(IAsyncEnumerable<T> asyncEnumerable, Action<T> action, Cancellatio
 394        {
 395#if NET6_0_OR_GREATER
 396            ArgumentNullException.ThrowIfNull(asyncEnumerable);
 397#else
 398            if (asyncEnumerable == null) throw new ArgumentNullException(nameof(asyncEnumerable));
 399#endif
 400#if NET6_0_OR_GREATER
 401            ArgumentNullException.ThrowIfNull(action);
 402#else
 403            if (action == null) throw new ArgumentNullException(nameof(action));
 404#endif
 405            IAsyncEnumerator<T> e = asyncEnumerable.GetAsyncEnumerator(cancel);
 406            try
 407            {
 408                while (await Run(() => e.MoveNextAsync()))
 409                {
 410                    cancel.ThrowIfCancellationRequested();
 411                    action(e.Current);
 412                }
 413            }
 414            finally { if (e != null) await Run(() => e.DisposeAsync()); }
 415        }
 416        /// <summary>
 417        /// Iterates through the specified async enumerable using the ambient synchronicity and an asynchronous delegate
 418        /// </summary>
 419        /// <typeparam name="T">The type being enumerated.</typeparam>
 420        /// <param name="asyncEnumerable">The <see cref="IAsyncEnumerable{T}"/> to enumerate.</param>
 421        /// <param name="func">The async action to perform on each enumerated item.</param>
 422        /// <param name="cancel">A <see cref="CancellationToken"/> that may be used to interrupt the enumeration.</param
 423        /// <returns>A <see cref="ValueTask"/> for the iteration.</returns>
 424        /// <exception cref="ArgumentNullException">If <paramref name="asyncEnumerable"/> or <paramref name="func"/> are
 425        [DebuggerStepThrough]
 426        public static async ValueTask AwaitForEach<T>(IAsyncEnumerable<T> asyncEnumerable, Func<T, CancellationToken, Va
 427        {
 428#if NET6_0_OR_GREATER
 429            ArgumentNullException.ThrowIfNull(asyncEnumerable);
 430#else
 431            if (asyncEnumerable == null) throw new ArgumentNullException(nameof(asyncEnumerable));
 432#endif
 433#if NET6_0_OR_GREATER
 434            ArgumentNullException.ThrowIfNull(func);
 435#else
 436            if (func == null) throw new ArgumentNullException(nameof(func));
 437#endif
 438            IAsyncEnumerator<T> e = asyncEnumerable.GetAsyncEnumerator(cancel);
 439            try
 440            {
 441                while (await Run(() => e.MoveNextAsync()))
 442                {
 443                    cancel.ThrowIfCancellationRequested();
 444                    await Run(() => func(e.Current, cancel));
 445                }
 446            }
 447            finally { if (e != null) await Run(() => e.DisposeAsync()); }
 448        }
 449#endif
 450    }
 451    /// <summary>
 452    /// A static class to hold extensions to IEnumerable.
 453    /// </summary>
 454    public static class IEnumerableExtensions
 455    {
 456        /// <summary>
 457        /// Converts a single item to an enumerable.
 458        /// </summary>
 459        /// <typeparam name="T">The type for the item.</typeparam>
 460        /// <param name="singleItem">The item to put into an enumerable.</param>
 461        /// <returns>An enumerable with just <paramref name="singleItem"/> in it.</returns>
 462        public static IEnumerable<T> ToSingleItemEnumerable<T>(this T singleItem)
 463        {
 464            yield return singleItem;
 465        }
 466#if NETSTANDARD2_1 || NETCOREAPP3_1_OR_GREATER || NET5_0_OR_GREATER
 467        /// <summary>
 468        /// Converts an enumerable into an async enumerable.  Works with very large (or even infinite) enumerations.
 469        /// </summary>
 470        /// <typeparam name="T">The type for the item.</typeparam>
 471        /// <param name="e">The enumerable.</param>
 472        /// <param name="cancel">A <see cref="CancellationToken"/> the caller can use to cancel the operation before it 
 473        /// <returns>An async enumerable with the elements from <paramref name="e"/> in it.</returns>
 474        public static async IAsyncEnumerable<T> ToAsyncEnumerable<T>(this IEnumerable<T> e, [EnumeratorCancellation] Can
 475        {
 476#if NET6_0_OR_GREATER
 477            ArgumentNullException.ThrowIfNull(e);
 478#else
 479            if (e == null) throw new ArgumentNullException(nameof(e));
 480#endif
 481            foreach (T t in e)
 482            {
 483                cancel.ThrowIfCancellationRequested();
 484                yield return t;
 485            }
 486            await Task.CompletedTask;
 487        }
 488#endif
 489    }
 490    /// <summary>
 491    /// A static class to hold extensions to IEnumerable.
 492    /// </summary>
 493    public static class IEnumeratorExtensions
 494    {
 495#if NETSTANDARD2_1 || NETCOREAPP3_1_OR_GREATER || NET5_0_OR_GREATER
 496        /// <summary>
 497        /// Converts an enumerable into an async enumerable.  Works with very large (or even infinite) enumerations.
 498        /// </summary>
 499        /// <typeparam name="T">The type for the item.</typeparam>
 500        /// <param name="e">The enumerable.</param>
 501        /// <param name="cancel">A <see cref="CancellationToken"/> the caller can use to cancel the operation before it 
 502        /// <returns>An async enumerable with the elements from <paramref name="e"/> in it.</returns>
 503        public static async IAsyncEnumerable<T> ToAsyncEnumerable<T>(this IEnumerator<T> e, [EnumeratorCancellation] Can
 504        {
 505#if NET6_0_OR_GREATER
 506            ArgumentNullException.ThrowIfNull(e);
 507#else
 508            if (e == null) throw new ArgumentNullException(nameof(e));
 509#endif
 510            while (e.MoveNext())
 511            {
 512                cancel.ThrowIfCancellationRequested();
 513                yield return e.Current;
 514            }
 515            await Task.CompletedTask;
 516        }
 517#endif
 518    }
 519#if NETSTANDARD2_1 || NETCOREAPP3_1_OR_GREATER || NET5_0_OR_GREATER
 520    /// <summary>
 521    /// A static class to hold extensions to IAsyncEnumerable.
 522    /// </summary>
 523    public static class IAsyncEnumerableExtensions
 524    {
 525        /// <summary>
 526        /// Converts a single item to an enumerable.
 527        /// </summary>
 528        /// <typeparam name="T">The type for the item.</typeparam>
 529        /// <param name="singleItem">The item to put into an enumerable.</param>
 530        /// <returns>An enumerable with just <paramref name="singleItem"/> in it.</returns>
 531#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
 532        public static async IAsyncEnumerable<T> ToSingleItemAsyncEnumerable<T>(this T singleItem)
 533#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
 534        {
 1535            yield return singleItem;
 1536        }
 537    }
 538    /// <summary>
 539    /// A static class to hold extensions to IAsyncEnumerator.
 540    /// </summary>
 541    public static class IAsyncEnumeratorExtensions
 542    {
 543        /// <summary>
 544        /// Asynchronously converts an <see cref="IAsyncEnumerable{T}"/> into a list.
 545        /// Note that since it returns a list, this function does NOT work with inifinite (or even very large) enumerati
 546        /// </summary>
 547        /// <typeparam name="T">The type within the list.</typeparam>
 548        /// <param name="ae">The <see cref="IAsyncEnumerable{T}"/>.</param>
 549        /// <param name="cancel">A <see cref="CancellationToken"/> the caller can use to cancel the operation before it 
 550        /// <returns>A <see cref="List{T}"/> containing all the items in the async enumerator.</returns>
 551        public static async ValueTask<List<T>> ToListAsync<T>(this IAsyncEnumerator<T> ae, CancellationToken cancel = de
 552        {
 553#if NET6_0_OR_GREATER
 554            ArgumentNullException.ThrowIfNull(ae);
 555#else
 556            if (ae == null) throw new ArgumentNullException(nameof(ae));
 557#endif
 558            List<T> ret = new();
 559            while (await ae.MoveNextAsync())
 560            {
 561                cancel.ThrowIfCancellationRequested();
 562                ret.Add(ae.Current);
 563            }
 564            return ret;
 565        }
 566        /// <summary>
 567        /// Asynchronously converts an <see cref="IAsyncEnumerator{T}"/> into an array.
 568        /// Note that since it returns a array, this function does NOT work with inifinite (or even very large) enumerat
 569        /// </summary>
 570        /// <typeparam name="T">The type within the list.</typeparam>
 571        /// <param name="ae">The <see cref="IAsyncEnumerator{T}"/>.</param>
 572        /// <param name="cancel">A <see cref="CancellationToken"/> the caller can use to cancel the operation before it 
 573        /// <returns>An array containing all the items in the async enumerator.</returns>
 574        public static async ValueTask<T[]> ToArrayAsync<T>(this IAsyncEnumerator<T> ae, CancellationToken cancel = defau
 575        {
 576#if NET6_0_OR_GREATER
 577            ArgumentNullException.ThrowIfNull(ae);
 578#else
 579            if (ae == null) throw new ArgumentNullException(nameof(ae));
 580#endif
 581            List<T> ret = new();
 582            while (await ae.MoveNextAsync())
 583            {
 584                cancel.ThrowIfCancellationRequested();
 585                ret.Add(ae.Current);
 586            }
 587            return ret.ToArray();
 588        }
 589        /// <summary>
 590        /// Asynchronously converts an <see cref="IAsyncEnumerator{T}"/> into an <see cref="IEnumerable{T}"/>.
 591        /// Note that since it returns a array, this function does NOT work with inifinite (or even very large) enumerat
 592        /// </summary>
 593        /// <typeparam name="T">The type within the list.</typeparam>
 594        /// <param name="ae">The <see cref="IAsyncEnumerator{T}"/>.</param>
 595        /// <param name="cancel">A <see cref="CancellationToken"/> the caller can use to cancel the operation before it 
 596        /// <returns>An enumeration of all the items in the async enumerator.</returns>
 597        public static async ValueTask<IEnumerable<T>> ToEnumerableAsync<T>(this IAsyncEnumerator<T> ae, CancellationToke
 598        {
 599            // there may be a more efficient way to do this, but I haven't been able to find it
 600            return await Async.Run(() => ToListAsync<T>(ae, cancel));
 601        }
 602    }
 603#endif
 604    /// <summary>
 605    /// A <see cref="SynchronousTaskScheduler"/> that just runs each task immediately as it is queued.
 606    /// </summary>
 607    public sealed class SynchronousTaskScheduler : System.Threading.Tasks.TaskScheduler
 608    {
 609        private static readonly SynchronousTaskScheduler _Default = new();
 610        /// <summary>
 611        /// Gets the default instance for this singleton class.
 612        /// </summary>
 613        public static new SynchronousTaskScheduler Default => _Default;
 614
 615        private SynchronousTaskScheduler()
 616        {
 617        }
 618        /// <summary>
 619        /// Gets the list of scheduled tasks, which for this class, is always empty.
 620        /// </summary>
 621        /// <returns>An empty enumeration.</returns>
 622        [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] //  (no way to call externally, and I can't find a way
 623        protected override IEnumerable<Task> GetScheduledTasks()
 624        {
 625            return Enumerable.Empty<Task>();
 626        }
 627        /// <summary>
 628        /// Queues the specified task, which in this case, just executes it immediately.
 629        /// </summary>
 630        /// <param name="task">The <see cref="Task"/> which is to be executed.</param>
 631        [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] //  (no way to call externally, and I can't find a way
 632        protected override void QueueTask(Task task)
 633        {
 634            TryExecuteTask(task);
 635        }
 636        /// <summary>
 637        /// Attempts to execute the specified task inline, which just runs the task.
 638        /// </summary>
 639        /// <param name="task">The <see cref="Task"/> to be executed.</param>
 640        /// <param name="taskWasPreviouslyQueued">Whether or not the task was previously queued.</param>
 641        /// <returns><b>true</b>.</returns>
 642        [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] //  (no way to call externally, and I can't find a way
 643        protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
 644        {
 645            return TryExecuteTask(task);
 646        }
 647        /// <summary>
 648        /// Gets the maximum number of tasks that can be concurrently running under this scheduler, which is one.
 649        /// </summary>
 650        public override int MaximumConcurrencyLevel => 1;
 651    }
 652
 653    /// <summary>
 654    /// A <see cref="SynchronousSynchronizationContext"/> that schedules work items on the <see cref="SynchronousTaskSch
 655    /// </summary>
 656    public class SynchronousSynchronizationContext : System.Threading.SynchronizationContext
 657    {
 658        private static readonly SynchronousSynchronizationContext _Default = new();
 659        /// <summary>
 660        /// Gets the instance for this singleton class.
 661        /// </summary>
 662        public static SynchronousSynchronizationContext Default => _Default;
 663
 664        private SynchronousSynchronizationContext()
 665        {
 666            SetWaitNotificationRequired();
 667        }
 668
 669        /// <summary>
 670        /// Synchronously posts a message.
 671        /// </summary>
 672        /// <param name="d">The message to post.</param>
 673        /// <param name="state">The state to give to the post callback.</param>
 674        public override void Send(System.Threading.SendOrPostCallback d, object? state)
 675        {
 676#if NET6_0_OR_GREATER
 677            ArgumentNullException.ThrowIfNull(d);
 678#else
 679            if (d == null) throw new ArgumentNullException(nameof(d));
 680#endif
 681            d(state);
 682        }
 683        /// <summary>
 684        /// Posts a message.  The caller intended to post it asynchronously, but the whole point of this class is to do 
 685        /// </summary>
 686        /// <param name="d">The message to post.</param>
 687        /// <param name="state">The state to give to the post callback.</param>
 688        public override void Post(System.Threading.SendOrPostCallback d, object? state)
 689        {
 690#if NET6_0_OR_GREATER
 691            ArgumentNullException.ThrowIfNull(d);
 692#else
 693            if (d == null) throw new ArgumentNullException(nameof(d));
 694#endif
 695            d(state);
 696        }
 697        /// <summary>
 698        /// Creates a "copy" of this <see cref="SynchronousSynchronizationContext"/>, which in this case just returns th
 699        /// </summary>
 700        /// <returns>The same singleton <see cref="SynchronousSynchronizationContext"/> on which we were called.</return
 701        public override System.Threading.SynchronizationContext CreateCopy()
 702        {
 703            return this;
 704        }
 705    }
 706}

Methods/Properties

ToSingleItemAsyncEnumerable()