3

I have the following code.

var tasks = new[]
    {
        Task.Factory.StartNew(() => GetSomething1()),
        Task.Factory.StartNew(() => GetSomething2()),
        Task.Factory.StartNew(() => GetSomething3())
    };

var things = Task.WhenAll(tasks);

How can I get the results from all three tasks separately and print them?

Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
  • 1
    Please have a look here https://stackoverflow.com/questions/6123406/waitall-vs-whenall – Shahid Manzoor Bhat Dec 18 '19 at 14:57
  • If the `Get..` methods are all asynchronous (returning `Task`s of their own), you don't need `.StartNew`. If they're all synchronous and their number could be variable (based on other collections), consider using `Enumerable.AsParallel` (PLINQ) or `Parallel.For[Each]` to parallelize the work, rather than wrapping them as tasks. – Jeroen Mostert Dec 18 '19 at 15:05
  • @DmitryBychenko That's along the right path, but your call to `WhenAll` is superfluous if using `Result` on each task straight after. – Neo Nov 14 '20 at 23:41

4 Answers4

0

One way is to make each Task responsible for storing its own result, and then all you have to do is await the collection of Tasks. Note that you'll have to use await to make the WhenAll() execute the tasks that you pass into it.

var results = new int[3];

var tasks = new[] {
    Task.Factory.StartNew(() => results[0] = GetSomething1()), 
    Task.Factory.StartNew(() => results[1] = GetSomething2()), 
    Task.Factory.StartNew(() => results[2] = GetSomething3())
};

await Task.WhenAll(tasks);

Console.WriteLine(results[0]);
Console.WriteLine(results[1]);
Console.WriteLine(results[2]);

Working demo: https://dotnetfiddle.net/HS32QA

Note that you may need to be careful with using e.g. a List<T> instead of an array, and then calling list.Add(result), as there is no guarantee of the order in which the tasks are executed or when they will be finished.

Peter B
  • 22,460
  • 5
  • 32
  • 69
  • The real issue with `List.Add` is not any of order but the simple fact that it's not thread-safe. You'd need explicit locks or something like `ConcurrentBag` to work around that. However, by that point an approach like this is probably unnecessarily complicated and only worth considering if the number of results per task is variable. Otherwise, there's no real reason not to use the fact that tasks have built-in support for results (as used in the other answers). – Jeroen Mostert Dec 18 '19 at 15:08
0

You should use async..await pattern with When, e.g.

  private async Task MyExecution() {
    var tasks = new[] {
      //TODO: Task.Run is a better option than Task.Factory.StartNew
      Task.Factory.StartNew(() => GetSomething1()),
      Task.Factory.StartNew(() => GetSomething2()),
      Task.Factory.StartNew(() => GetSomething3())
    };

    // Await until all tasks are completed 
    await Task.WhenAll(tasks);

    // Since all tasks are completed, we can (safely) query for their `Result`s:
    var things = tasks
      .Select(task => task.Result) // or task => await task
      .ToArray();

    // Let's print the things
    for (int i = 0; i < things.Length; ++i) 
      Console.WriteLine($"Task #{i + 1} returned {things[i]}");

    ... 
  }
Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
  • 1
    [`Task.WhenAll`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.whenall#System_Threading_Tasks_Task_WhenAll__1_System_Collections_Generic_IEnumerable_System_Threading_Tasks_Task___0___) returns a `Task`. Awaiting this task returns an array with the results. No need for the `.Select.ToArray` stuff. – Theodor Zoulias Dec 19 '19 at 00:28
0

In order to get the results seperately you have multiple ways I would do something like this:

var task1  = GetSomething1();
var task2  = GetSomething2();
var task3  = GetSomething3();

// your method will continue when everything's completed, but you won't tie up a thread to just hang around until that time.
await Task.WhenAll(task1, task2, task3);

var result1 = await task1;
var result2 = await task2;
var result3 = await task3;
Shahid Manzoor Bhat
  • 1,307
  • 1
  • 13
  • 32
  • 1
    This doesn't answer the question, and won't compile if the `GetSomethingX` methods don't return `Task`s (which I doubt they do). If they do, then the call to `WhenAll` is superfluous, especially as it's not clear if `GetSomethingX` is returning `Task` or `Task>`. – Neo Nov 14 '20 at 23:39
-1

Have you considered actually using async functions? Then you have an array of tasks with the results, and avoid the highly unpredictable behaviours of Task.Factory.StartNew.

private async Task MyExecution() 
{
    var tasks = new[] {
        GetSomething1(),
        GetSomething2(),
        GetSomething3()
    };

    // Await until all tasks are completed 
    await Task.WhenAll(tasks);

    foreach(var t in tasks){
        //t.Result is available
    }
}

public static async Task<int> GetSomething1() { return 1; }
public static async Task<int> GetSomething2() { return 2; }
public static async Task<int> GetSomething3() { return 3; }

Fiddle here: https://dotnetfiddle.net/3ffs9L

IOrlandoni
  • 1,790
  • 13
  • 30
  • 2
    What would those "highly unpredictable behaviors" be? Wrapping synchronous code has its issues, but those are not specific to `StartNew`. The `async` methods you offer by way of example are not themselves asynchronous, and could mislead people into thinking that slapping on `async Task` is enough to turn a synchronous function into an asynchronous one. In reality, in this particular code snippet the results are all available whether or not you ever call `await Task.WhenAll`, as the code is executed synchronously when the array is created. – Jeroen Mostert Dec 18 '19 at 15:17
  • @JeroenMostert Not sure why you think the original code wraps synchronous code. Some of the problems with StartNew can be found [here](https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html), [here](https://sergeyteplyakov.github.io/Blog/async/2019/05/21/The-Dangers-of-Task.Factory.StartNew.html), and [here](https://devblogs.microsoft.com/pfxteam/task-run-vs-task-factory-startnew/). The async methods I offer are simply an example and you're correct: they run synchronously. In fact, the compiler will warn of precisely that. – IOrlandoni Dec 18 '19 at 15:32
  • You're right, that's an assumption based on the fact that if the `Get...` methods already returned `Task`s, there'd be no reason to plug them into `.StartNew()` in the first place -- but the OP might not have been aware of that. Your answer would be improved by actually linking to the problems of `.StartNew()` in there -- I wouldn't call those pitfalls "unpredictable"! – Jeroen Mostert Dec 18 '19 at 15:37