74

I am using a console app as a proof of concept and new need to get an async return value.

I figured out that I need to use Task.WaitAll() in my main method to avoid needing an async "main()" method, which is illegal.

I'm now stuck trying to figure out an overload that allows me to use generics or just returns an object that I can cast, but while in Main().

i3arnon
  • 113,022
  • 33
  • 324
  • 344
makerofthings7
  • 60,103
  • 53
  • 215
  • 448

3 Answers3

119

You don't get a return value from Task.WaitAll. You only use it to wait for completion of multiple tasks and then get the return value from the tasks themselves.

var task1 = GetAsync(1);
var task2 = GetAsync(2);
Task.WaitAll(task1, task2);
var result1 = task1.Result;
var result2 = task2.Result;

If you only have a single Task, just use the Result property. It will return your value and block the calling thread if the task hasn't finished yet:

var task = GetAsync(3);
var result = task.Result;

It's generally not a good idea to synchronously wait (block) on an asynchronous task ("sync over async"), but I guess that's fine for a POC.

i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • 2
    Not sure if there has been a change in the implementation of `Task.WaitAll()` but this answer didn't work for me. `Task.WaitAll()` would just block the thread and execution never got to the next line. As suggested in other answer doing `await Task.WhenAll()` seems to be the recommended way going forward. – Adrian Hristov May 25 '17 at 15:32
  • 3
    @AdrianHristov nothing changed, you probably just have a `SynchronizationContext` deadlock in your code. As this answer states, it's not a good idea to block on an asynchronous task, but if you decide to do so this is how you get the task results. – i3arnon May 25 '17 at 17:30
  • I dont see the difference between something like: for(var i = 0; i < 100; i++) { taskList.Add(client.PostAsync("http://localhost:3000...)); } await Task.WhenAll(taskList); foreach(var task in taskList) { System.Diagnostics.Debug.WriteLine(task.Result); } I get an error in getting task.Result as there isn't a Result property or method on Task? – Kevin Burton May 18 '18 at 23:38
  • @KevinBurton for a return type of Task, you can use Wait() [or one of the overloads]. for a return type of Task, where T is a return type, you can use Result. – sscheider Sep 07 '18 at 18:23
60

For best practice, use the new async way of doing things. Instead of

  • Task.WaitAll use await Task.WhenAll
  • Task.WaitAny use await Task.WhenAny

The code above can be written as:

var task1 = GetAsync(1);
var task2 = GetAsync(2);
var results = await Task.WhenAll(task1, task2);
var result1 = results[0];
var result2 = results[1];
Vaccano
  • 78,325
  • 149
  • 468
  • 850
alltej
  • 6,787
  • 10
  • 46
  • 87
  • 6
    This forces the method that this code is in to have the async keyword and return type of Task. The Task.WaitAll will allow you to return void. I'm not saying whether thats better or not. – chris31389 Aug 09 '16 at 08:56
  • 3
    You should avoid returning Task void. – alltej Sep 19 '16 at 12:32
  • 4
    `Task.WaitAll` requires all the tasks to have **same return type** – tchelidze Jul 13 '17 at 06:23
  • 2
    As an update to this discussion, refer to: https://stackoverflow.com/questions/17197699/awaiting-multiple-tasks-with-different-results – James Reategui Nov 16 '17 at 02:45
  • 1
    You shouldn't say "instead of", because sometimes we want blocking async rather than non-blocking. `await` just made your thread non-blocking. – RandomEli Dec 22 '17 at 20:32
  • For future aid; if all tasks return a boolean, everything can be returned with a: `var totalResult = results.All(b => b)`. (I write this because tasks can be somewhat magical and I want to show that the result has lost that spooky aura) – LosManos Jan 03 '22 at 10:46
1

There are different methods you can use depending on the context and your needs, but I tried out the answers from i3arnon and alltej, and in debugging I found that they both have the same problem... I ran both code samples in a console app I made. I set a break point in the code and found that on the line of execution with Task.WaitAll or Task.WhenAll. When I examined the tasks I found that they had already run - the task property Status was = ran to completion. In fact, I was able to completely remove the Task. line of code and still assign the variables values, so it was useless.

I found it was a very difficult task to truly make this kind of operation run as desired and also get the return values needed. Here is what I came up with:

Task[] taskArray = {
    Task.Factory.StartNew(() => ExecuteConcurrentTasks(1).Result),
    Task.Factory.StartNew(() => ExecuteConcurrentTasks(2).Result)
};
Task.WaitAll(taskArray);
var s = ((Task<string>)taskArray[0]).Result;

This is a simple example where a task array is created, then Task.WaitAll is used to execute the 2 tasks simultaneously, then in the last line I'm showing how to access the returned value from the first task.

Here is the code with some more context and more data:

private static async void MyAsyncMethod()
{
    Task[] taskArray = {
        Task.Factory.StartNew(() => ExecuteConcurrentTasks(1).Result),
        Task.Factory.StartNew(() => ExecuteConcurrentTasks(2).Result),
        Task.Factory.StartNew(() => ExecuteConcurrentTasks(3).Result),
        Task.Factory.StartNew(() => ExecuteConcurrentTasks(4).Result)
    };
    Task.WaitAll(taskArray);
    string s1 = ((Task<string>)taskArray[0]).Result;
    string s2 = ((Task<string>)taskArray[1]).Result;
    string s3 = ((Task<string>)taskArray[2]).Result;
    string s4 = ((Task<string>)taskArray[3]).Result;
}



private static async Task<string> ExecuteConcurrentTasks(int passedInt)
{
    string s = "Result: " + passedInt.ToString();

    if (passedInt == 4)
    {
        Console.WriteLine(s);
    }
    else if (passedInt == 3)
    {
        Console.WriteLine(s);
    }
    else if (passedInt == 2)
    {
        Console.WriteLine(s);
    }
    else if (passedInt == 1)
    {
        Console.WriteLine(s);
    }

    return s;
}

Here is the output:

output


UPDATE:

I got a bit more insight on these methods from working with some other developers. My understanding now is that .WaitAll will block execution in other parts of the program, which is an issue if you're not running just a very simple program. Also .WhenAll doesn't mean that the tasks won't execute until that line and all begin asynchronous execution at that line, it just means that the code in that block won't continue beyond that point unless all the specified tasks have finished running.

SendETHToThisAddress
  • 2,756
  • 7
  • 29
  • 54
  • 1
    i upvoted this because it shows you have to cast a task in an array to get the result value. I don't understand why you do, since the array is of that type, but you do. – John Lord Jan 02 '22 at 23:53
  • 1
    The reason for my downvote is that the `Task.Factory.StartNew(() => ExecuteConcurrentTasks(1).Result)` blocks a `ThreadPool` thread for no reason, and uses the [primitive](https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html) `Task.Factory.StartNew` method without specifying explicitly the `TaskScheduler`. The correct is `Task.Run(() => ExecuteConcurrentTasks(1))`, or simply `ExecuteConcurrentTasks(1)`. – Theodor Zoulias Jan 08 '22 at 15:15