3

Let's say I have a list of Tasks, and I want to run them in parallel. But I don't need all of them to finish to continue, I can move on with just one. The following code waits for all the tasks to finish to move on. Is there a way for me to individually continue with the task that has completed while waiting for the other ones to finish?

List<string>[] x = await Task.WhenAll(new Task<List<string>>[] { task1, task2 })
// When task1 finishes, I want to process the result immediately
// instead of waiting on task2.
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
idude
  • 4,654
  • 8
  • 35
  • 49

5 Answers5

6

You're probably looking for Task.WhenAny.

I've used it for setting off a pile of tasks and then processing each of them as they become ready, but I suppose you could also just wait for one to finish and continue without the loop if you don't care about dealing with the rest.

while (tasks.Count() > 0)
{
    var task = await Task.WhenAny(tasks);
    tasks.Remove(task);
    var taskresult = await task;
    // process result
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Richard Ward
  • 360
  • 3
  • 14
5

If you are using C# 8 and .NET Core you can take advantage of IAsyncEnumerable to hide this complexity from the consuming side.

Just like this:

static async Task Main(string[] args)
{
    await foreach (var data in GetData())
    {
        Console.WriteLine(data);
    }

    Console.ReadLine();
}

static async IAsyncEnumerable<string> GetData()
{
    List<Task<string>> tasks = new List<Task<string>> {GetData1(), GetData3(), GetData2()};

    while (tasks.Any())
    {
        var finishedTask = await Task.WhenAny(tasks);
        tasks.Remove(finishedTask);
        yield return await finishedTask;
    }
}

static async Task<string> GetData1()
{
    await Task.Delay(5000);
    return "Data1";
}

static async Task<string> GetData2()
{
    await Task.Delay(3000);
    return "Data2";
}

static async Task<string> GetData3()
{
    await Task.Delay(2000);
    return "Data3";
}
Peter Csala
  • 17,736
  • 16
  • 35
  • 75
2

You can use Task.WhenAny instead.

Example "stolen" from Stephen Cleary's Blog:

var client = new HttpClient();
string results = await await Task.WhenAny(
    client.GetStringAsync("http://example.com"),
    client.GetStringAsync("http://microsoft.com"));
// results contains the HTML for whichever website responded first.

Responding to comment

You absolutely can keep track of the other tasks:

// supposing you have a list of Tasks in `myTasks`:
while (myTasks.Count > 0)
{
    var finishedTask = await Task.WhenAny(myTasks);
    myTasks.Remove(finishedTask);
    handleFinishedTask(finishedTask); // assuming this is a method that
                                      // does the work on finished tasks.
}

The only thing you'd have to watch out for is:

The returned task will always end in the RanToCompletion state with its Result set to the first task to complete. This is true even if the first task to complete ended in the Canceled or Faulted state.

Remarks in Task.WhenAny docs (emphasis by me).

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Fildor
  • 14,510
  • 4
  • 35
  • 67
  • Sounds good, but I still want the response from the ones that didn't finish first. Is there any way to do that? – idude Jul 01 '20 at 14:01
  • Absolutely. If you use just one `await` you'll get the Task that completed and can remove it from the list. Quite as in Richard's answer. In Stephen Cleary's Blog are many more examples. It's really worth a read, as is anything from that author. – Fildor Jul 01 '20 at 14:03
1

In case you want to process the results in order of the completion of the tasks, there is the OrderByCompletion extension method that does exactly that in Stephen Cleary's Nito.AsyncEx library, with the signature below:

// Creates a new collection of tasks that complete in order.
public static List<Task<T>> OrderByCompletion<T>(this IEnumerable<Task<T>> @this);

Usage example:

Task<string>[] tasks = new[] { task1, task2, task3, task4 };
foreach (var task in tasks.OrderByCompletion())
{
    string result = await task;
    // Do something with result
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
0

Based on the answer of Peter Csala, here a extension method for IAsyncEnumerable:

    public static async IAsyncEnumerable<T> OrderedByCompletion<T>(this IEnumerable<Task<T>> tasks)
    {
        List<Task<T>> taskList = new List<Task<T>>(tasks);

        while (taskList.Count > 0)
        {
            var finishedTask = await Task.WhenAny(taskList);
            taskList.Remove(finishedTask);
            yield return await finishedTask;
        }
    }
Felix K.
  • 6,201
  • 2
  • 38
  • 71
  • 3
    Be aware that the `await Task.WhenAny`-in-a-loop is an [inefficient antipattern](https://stackoverflow.com/questions/72271006/ "Task.WhenAny - alternative to List avoiding O(N²) issues") with O(n²) complexity. It works, but it scales badly. – Theodor Zoulias Jun 15 '22 at 23:39
  • @TheodorZoulias Yes. You can optimize this a lot, but as long as this isn't performance critical i would suggest to just stick with that. There are much fast ways to implement this, starting with using a dictionary and finishing with using a different implementation which doesn't run a loop. – Felix K. Jun 16 '22 at 18:36
  • Felix even if you switch from `List` to `Dictionary`, the complexity of this pattern is still O(n²). – Theodor Zoulias Jun 16 '22 at 19:02
  • @TheodorZoulias Yes, as i said you can also implement this completely different, however it only worth the work if you really require that extra bit of performance. – Felix K. Jun 16 '22 at 19:15
  • Felix you don't have to do anything special. It's already implemented efficiently, at least three times. Just pick one of [Jon Skeet](https://codeblog.jonskeet.uk/2012/01/16/eduasync-part-19-ordering-by-completion-ahead-of-time/)'s, [Stephen Toub](https://devblogs.microsoft.com/pfxteam/processing-tasks-as-they-complete/)'s or [Stephen Cleary](https://github.com/StephenCleary/AsyncEx/blob/0361015459938f2eb8f3c1ad1021d19ee01c93a4/src/Nito.AsyncEx.Tasks/TaskExtensions.cs#L179-L260)'s implementation and you are done. – Theodor Zoulias Jun 16 '22 at 19:36
  • No, this is `IAsyncEnumerable`, which isn't the same as `IEnumerable`. Yes in the end it's almost the same results wise, still the way you write it and the way it can be used are different. I know Skeet's implementation and used it in the past. – Felix K. Jun 16 '22 at 21:31
  • Ah, so you didn't answer exactly what is asked in the question, but something closely related to this. A question where your answer would be a better fit is this: [How to implement an efficient WhenEach that streams an IAsyncEnumerable of task results?](https://stackoverflow.com/questions/58194212/how-to-implement-an-efficient-wheneach-that-streams-an-iasyncenumerable-of-task) – Theodor Zoulias Jun 16 '22 at 21:46