2020-12-28 14:45:25 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
using System ;
using System.Collections.Generic ;
2022-10-24 16:16:37 -04:00
using System.Runtime.CompilerServices ;
2021-10-16 15:10:01 -04:00
using System.Threading ;
2020-12-28 14:45:25 -04:00
using System.Threading.Tasks ;
namespace EpicGames.Core
{
/// <summary>
/// Utility functions for manipulating async tasks
/// </summary>
public static class AsyncUtils
{
2021-10-16 15:10:01 -04:00
/// <summary>
/// Converts a cancellation token to a waitable task
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="token">Cancellation token</param>
2021-10-16 15:10:01 -04:00
/// <returns></returns>
2022-03-24 16:35:00 -04:00
public static Task AsTask ( this CancellationToken token )
2021-10-16 15:10:01 -04:00
{
2022-10-03 17:01:13 -04:00
return Task . Delay ( - 1 , token ) . ContinueWith ( x = > { } , TaskScheduler . Default ) ;
2021-10-16 15:10:01 -04:00
}
2022-03-28 19:03:36 -04:00
/// <summary>
/// Converts a cancellation token to a waitable task
/// </summary>
/// <param name="token">Cancellation token</param>
/// <returns></returns>
public static Task < T > AsTask < T > ( this CancellationToken token )
{
2022-10-03 17:01:13 -04:00
return Task . Delay ( - 1 , token ) . ContinueWith ( _ = > Task . FromCanceled < T > ( token ) , TaskScheduler . Default ) . Unwrap ( ) ;
2022-03-28 19:03:36 -04:00
}
2023-02-24 17:55:04 -05:00
/// <summary>
/// Waits for a task to complete, ignoring any cancellation exceptions
/// </summary>
/// <param name="task">Task to wait for</param>
public static async Task IgnoreCanceledExceptionsAsync ( this Task task )
{
try
{
await task . ConfigureAwait ( false ) ;
}
catch ( OperationCanceledException )
{
}
}
2022-09-28 13:58:49 -04:00
/// <summary>
/// Returns a task that will be abandoned if a cancellation token is activated. This differs from the normal cancellation pattern in that the task will run to completion, but waiting for it can be cancelled.
/// </summary>
/// <param name="task">Task to wait for</param>
/// <param name="cancellationToken">Cancellation token for the operation</param>
/// <returns>Wrapped task</returns>
public static async Task < T > AbandonOnCancel < T > ( this Task < T > task , CancellationToken cancellationToken )
{
if ( cancellationToken . CanBeCanceled )
{
await await Task . WhenAny ( task , Task . Delay ( - 1 , cancellationToken ) ) ; // Double await to ensure cancellation exception is rethrown if returned
}
return await task ;
}
2021-12-14 18:44:21 -05:00
/// <summary>
/// Attempts to get the result of a task, if it has finished
/// </summary>
/// <typeparam name="T"></typeparam>
2022-03-24 16:35:00 -04:00
/// <param name="task"></param>
/// <param name="result"></param>
2021-12-14 18:44:21 -05:00
/// <returns></returns>
2022-03-24 16:35:00 -04:00
public static bool TryGetResult < T > ( this Task < T > task , out T result )
2021-12-14 18:44:21 -05:00
{
2022-03-24 16:35:00 -04:00
if ( task . IsCompleted )
2021-12-14 18:44:21 -05:00
{
2022-03-24 16:35:00 -04:00
result = task . Result ;
2021-12-14 18:44:21 -05:00
return true ;
}
else
{
2022-03-24 16:35:00 -04:00
result = default ! ;
2021-12-14 18:44:21 -05:00
return false ;
}
}
2021-10-16 15:10:01 -04:00
/// <summary>
/// Waits for a time period to elapse or the task to be cancelled, without throwing an cancellation exception
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="time">Time to wait</param>
/// <param name="token">Cancellation token</param>
2021-10-16 15:10:01 -04:00
/// <returns></returns>
2022-03-24 16:35:00 -04:00
public static Task DelayNoThrow ( TimeSpan time , CancellationToken token )
2021-10-16 15:10:01 -04:00
{
2022-10-03 17:01:13 -04:00
return Task . Delay ( time , token ) . ContinueWith ( x = > { } , CancellationToken . None , TaskContinuationOptions . None , TaskScheduler . Default ) ;
2021-10-16 15:10:01 -04:00
}
2020-12-28 14:45:25 -04:00
/// <summary>
/// Removes all the complete tasks from a list, allowing each to throw exceptions as necessary
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="tasks">List of tasks to remove tasks from</param>
public static void RemoveCompleteTasks ( this List < Task > tasks )
2020-12-28 14:45:25 -04:00
{
2022-03-24 16:35:00 -04:00
List < Exception > exceptions = new List < Exception > ( ) ;
2021-06-03 10:55:49 -04:00
2022-03-24 16:35:00 -04:00
int outIdx = 0 ;
for ( int idx = 0 ; idx < tasks . Count ; idx + + )
2020-12-28 14:45:25 -04:00
{
2022-03-24 16:35:00 -04:00
if ( tasks [ idx ] . IsCompleted )
2020-12-28 14:45:25 -04:00
{
2022-03-24 16:35:00 -04:00
AggregateException ? exception = tasks [ idx ] . Exception ;
if ( exception ! = null )
2021-06-03 10:55:49 -04:00
{
2022-03-24 16:35:00 -04:00
exceptions . AddRange ( exception . InnerExceptions ) ;
2021-06-03 10:55:49 -04:00
}
2020-12-28 14:45:25 -04:00
}
else
{
2022-03-24 16:35:00 -04:00
if ( idx ! = outIdx )
2020-12-28 14:45:25 -04:00
{
2022-03-24 16:35:00 -04:00
tasks [ outIdx ] = tasks [ idx ] ;
2020-12-28 14:45:25 -04:00
}
2022-03-24 16:35:00 -04:00
outIdx + + ;
2020-12-28 14:45:25 -04:00
}
}
2022-03-24 16:35:00 -04:00
tasks . RemoveRange ( outIdx , tasks . Count - outIdx ) ;
2021-06-03 10:55:49 -04:00
2022-03-24 16:35:00 -04:00
if ( exceptions . Count > 0 )
2021-06-03 10:55:49 -04:00
{
2022-03-24 16:35:00 -04:00
throw new AggregateException ( exceptions ) ;
2021-06-03 10:55:49 -04:00
}
2020-12-28 14:45:25 -04:00
}
/// <summary>
/// Removes all the complete tasks from a list, allowing each to throw exceptions as necessary
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="tasks">List of tasks to remove tasks from</param>
2020-12-28 14:45:25 -04:00
/// <returns>Return values from the completed tasks</returns>
2022-03-24 16:35:00 -04:00
public static List < T > RemoveCompleteTasks < T > ( this List < Task < T > > tasks )
2020-12-28 14:45:25 -04:00
{
2022-03-24 16:35:00 -04:00
List < T > results = new List < T > ( ) ;
2020-12-28 14:45:25 -04:00
2022-03-24 16:35:00 -04:00
int outIdx = 0 ;
for ( int idx = 0 ; idx < tasks . Count ; idx + + )
2020-12-28 14:45:25 -04:00
{
2022-03-24 16:35:00 -04:00
if ( tasks [ idx ] . IsCompleted )
2020-12-28 14:45:25 -04:00
{
2022-03-24 16:35:00 -04:00
results . Add ( tasks [ idx ] . Result ) ;
2020-12-28 14:45:25 -04:00
}
2022-03-24 16:35:00 -04:00
else if ( idx ! = outIdx )
2020-12-28 14:45:25 -04:00
{
2022-03-24 16:35:00 -04:00
tasks [ outIdx + + ] = tasks [ idx ] ;
2020-12-28 14:45:25 -04:00
}
}
2022-03-24 16:35:00 -04:00
tasks . RemoveRange ( outIdx , tasks . Count - outIdx ) ;
2020-12-28 14:45:25 -04:00
2022-03-24 16:35:00 -04:00
return results ;
2020-12-28 14:45:25 -04:00
}
2022-10-24 16:16:37 -04:00
/// <summary>
/// Starts prefetching the next item from an async enumerator while the current one is being processes
/// </summary>
/// <typeparam name="T">Value type</typeparam>
/// <param name="source">Sequence to enumerate</param>
/// <param name="cancellationToken">Cancellation token for the operation</param>
/// <returns></returns>
public static async IAsyncEnumerable < T > Prefetch < T > ( this IAsyncEnumerable < T > source , [ EnumeratorCancellation ] CancellationToken cancellationToken )
{
await using IAsyncEnumerator < T > enumerator = source . GetAsyncEnumerator ( cancellationToken ) ;
if ( await enumerator . MoveNextAsync ( ) )
{
T value = enumerator . Current ;
for ( ; ; )
{
cancellationToken . ThrowIfCancellationRequested ( ) ;
Task < bool > task = enumerator . MoveNextAsync ( ) . AsTask ( ) ;
try
{
yield return value ;
}
finally
{
await task ; // Async state machine throws a NotSupportedException if disposed before awaiting this task
}
if ( ! await task )
{
break ;
}
value = enumerator . Current ;
}
}
}
/// <summary>
/// Starts prefetching a number of items from an async enumerator while the current one is being processes
/// </summary>
/// <typeparam name="T">Value type</typeparam>
/// <param name="source">Sequence to enumerate</param>
/// <param name="count">Number of items to prefetch</param>
/// <param name="cancellationToken">Cancellation token for the operation</param>
/// <returns></returns>
public static IAsyncEnumerable < T > Prefetch < T > ( this IAsyncEnumerable < T > source , int count , CancellationToken cancellationToken = default )
{
if ( count = = 0 )
{
return source ;
}
else
{
return Prefetch ( source , count - 1 , cancellationToken ) ;
}
}
2023-02-08 14:46:44 -05:00
/// <summary>
2023-08-08 13:29:31 -04:00
/// Waits for a native wait handle to be signaled
2023-02-08 14:46:44 -05:00
/// </summary>
/// <param name="handle">Handle to wait for</param>
/// <param name="cancellationToken">Cancellation token for the operation</param>
public static Task WaitOneAsync ( this WaitHandle handle , CancellationToken cancellationToken = default ) = > handle . WaitOneAsync ( - 1 , cancellationToken ) ;
/// <summary>
2023-08-08 13:29:31 -04:00
/// Waits for a native wait handle to be signaled
2023-02-08 14:46:44 -05:00
/// </summary>
/// <param name="handle">Handle to wait for</param>
/// <param name="timeoutMs">Timeout for the wait</param>
/// <param name="cancellationToken">Cancellation token for the operation</param>
public static async Task WaitOneAsync ( this WaitHandle handle , int timeoutMs , CancellationToken cancellationToken = default )
{
TaskCompletionSource < bool > completionSource = new TaskCompletionSource < bool > ( ) ;
RegisteredWaitHandle waitHandle = ThreadPool . RegisterWaitForSingleObject ( handle , ( state , timedOut ) = > ( ( TaskCompletionSource < bool > ) state ! ) . TrySetResult ( ! timedOut ) , completionSource , timeoutMs , true ) ;
try
{
using IDisposable registration = cancellationToken . Register ( x = > ( ( TaskCompletionSource < bool > ) x ! ) . SetCanceled ( ) , completionSource ) ;
await completionSource . Task ;
}
finally
{
waitHandle . Unregister ( null ) ;
}
}
2020-12-28 14:45:25 -04:00
}
}