Is there a default way to successfully complete the first task?

Suppose I have several tasks:

void Sample(IEnumerable<int> someInts) { var taskList = someInts.Select(x => DownloadSomeString(x)); } async Task<string> DownloadSomeString(int x) {...} 

I want to get the result of the first successful task. So the main solution is to write something like:

 var taskList = someInts.Select(x => DownloadSomeString(x)); string content = string.Empty; Task<string> firstOne = null; while (string.IsNullOrWhiteSpace(content)){ try { firstOne = await Task.WhenAny(taskList); if (firstOne.Status != TaskStatus.RanToCompletion) { taskList = taskList.Where(x => x != firstOne); continue; } content = await firstOne; } catch(...){taskList = taskList.Where(x => x != firstOne);} } 

But this solution seems to run N + ( N -1) + .. + K tasks. Where N someInts.Count and K is the position of the first successful task in tasks, since it restarts all tasks except one that is captured by WhenAny. So, is there a way to get the first task that successfully completed the execution of N tasks? (if the successful task is the last)

+6
source share
4 answers

The problem with the β€œfirst successful task” is what to do if all tasks fail? It is a really bad idea to complete a task that never ends .

I assume that you want to throw the last task exception if all of them fail. With that in mind, I would say something like this would be appropriate:

 async Task<Task<T>> FirstSuccessfulTask(IEnumerable<Task<T>> tasks) { Task<T>[] ordered = tasks.OrderByCompletion(); for (int i = 0; i != ordered.Length; ++i) { var task = ordered[i]; try { await task.ConfigureAwait(false); return task; } catch { if (i == ordered.Length - 1) return task; continue; } } return null; // Never reached } 

This solution is based on the OrderByCompletion extension method , which part is my AsyncEx library ; alternative implementations also exist for Jon Skeet and Stephen Toub .

+4
source

All you have to do is create a TaskCompletionSource , add a continuation to each of your tasks, and set it when the first is completed successfully:

 public static Task<T> FirstSuccessfulTask<T>(IEnumerable<Task<T>> tasks) { var taskList = tasks.ToList(); var tcs = new TaskCompletionSource<T>(); int remainingTasks = taskList.Count; foreach(var task in taskList) { task.ContinueWith(t => if(task.Status == TaskStatus.RanToCompletion) tcs.TrySetResult(t.Result)); else if(Interlocked.Decrement(ref remainingTasks) == 0) tcs.SetException(new AggregateException( tasks.SelectMany(t => t.Exception.InnerExceptions)); } return tcs.Task; } 

And the version for tasks without result:

 public static Task FirstSuccessfulTask(IEnumerable<Task> tasks) { var taskList = tasks.ToList(); var tcs = new TaskCompletionSource<bool>(); int remainingTasks = taskList.Count; foreach(var task in taskList) { task.ContinueWith(t => if(task.Status == TaskStatus.RanToCompletion) tcs.TrySetResult(true)); else if(Interlocked.Decrement(ref remainingTasks) == 0) tcs.SetException(new AggregateException( tasks.SelectMany(t => t.Exception.InnerExceptions)); } return tcs.Task; } 
+4
source

As a direct solution - wait for any task, check if it is in the RanToCompletion state, and if not, wait again for any task other than already completed.

  async Task<TResult> WaitForFirstCompleted<TResult>( IEnumerable<Task<TResult>> tasks ) { var taskList = new List<Task<TResult>>( tasks ); Task<TResult> firstCompleted; while ( taskList.Count > 0 ) { firstCompleted = await Task.WhenAny( taskList ); if ( firstCompleted.Status == TaskStatus.RanToCompletion ) { return firstCompleted.Result; } taskList.Remove( firstCompleted ); } throw new InvalidOperationException( "No task completed successful" ); } 
+2
source

A modified version of @Servy code, as it contains some compilation errors and several errors. My version:

 public static class AsyncExtensions { public static Task<T> GetFirstSuccessfulTask<T>(this IReadOnlyCollection<Task<T>> tasks) { var tcs = new TaskCompletionSource<T>(); int remainingTasks = tasks.Count; foreach (var task in tasks) { task.ContinueWith(t => { if (task.Status == TaskStatus.RanToCompletion) tcs.TrySetResult(t.Result); else if (Interlocked.Decrement(ref remainingTasks) == 0) tcs.SetException(new AggregateException( tasks.SelectMany(t2 => t2.Exception?.InnerExceptions ?? Enumerable.Empty<Exception>()))); }); } return tcs.Task; } } 

We do not need ToList our input, because it is already a team with which we can work, it compiles (a huge advantage) and handles the situation when an exception does not have a complete exception for some reason (this is quite possible).

0
source

All Articles