Is there default way to get first task that finished successfully?

The problem with "the first successful task" is what to do if all tasks fail? It's a really bad idea to have a task that never completes.

I assume you'd want to propagate the last task's exception if they all 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 builds on the OrderByCompletion extension method that is part of my AsyncEx library; alternative implementations also exist by Jon Skeet and Stephen Toub.


All you need to do is create a TaskCompletionSource, add a continuation to each of your tasks, and set it when the first one finished 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(t1 => t1.Exception.InnerExceptions)));
            });
    }
    return tcs.Task;
}

And a version for tasks without a 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(t1 => t1.Exception.InnerExceptions)));
        });
    }

    return tcs.Task;
}

As a straight forward solution is to wait for any task, check if it is in RanToCompletion state and if not, wait again for any task except the already finished one.

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