11

I am learning how to use async functions in console application but can't make the Task.WhenAll wait until all tasks are completed. What is wrong with the following code? It works synchronously. Thank you in advance.

static void Main(string[] args)
{
    ...
    IncluiValores(...);
    ...
}

static async void IncluiValores(...)
{
    Task<List<int>> res1 = att.GetAIDBAPI(att);
    Task<List<int>> res2 = att.GetAIDBAPI(att2);

    List<int>[] res = await Task.WhenAll(res1, res2);

    ...
}

UPDATE - Function Definition:

    public async Task<List<int>> GetAIDBAPI(Attributes attributes)
    {

        List<int> results = null;

        Connections client0 = new Connections();
        HttpClient client = client0.OpenAPIConnection(attributes.User[0], attributes.Pwd, attributes.Server, attributes.Chave, attributes.Server2);
        HttpResponseMessage response = await client.PostAsJsonAsync("api/Attributes/ID/Bulk", attributes);

        if (response.IsSuccessStatusCode)
        {
            var content = await response.Content.ReadAsStringAsync();
            results = JsonConvert.DeserializeObject<dynamic>(content).ToObject<List<int>>();
        }
        else
        {
            var content = "[{-1}]";
            var result = JsonConvert.DeserializeObject<dynamic>(content);
            results = result.ToObject<List<int>>();
        }

        return results;

    }

UPDATE 2 - Separate Context

static void Main(string[] args)
{
    AsyncContext.Run(() => MainAsync(args));
}

static async void MainAsync(string[] args)
{
    await IncluiValores(...);
}

static async Task IncluiValores(...)
{
    Task<List<int>> res1 = att.GetAIDBAPI(att);
    Task<List<int>> res2 = att.GetAIDBAPI(att2);

    List<int>[] res = await Task.WhenAll(res1, res2); // <- Error here 
    //Collection was modified; enumeration operation may not execute
    ...
}
//Tried to change to code below but it does not wait.
static async Task IncluiValores(...)
{
    Task<List<int>> res1 = att.GetAIDBAPI(att);
    Task<List<int>> res2 = att.GetAIDBAPI(att2);

    await Task.WhenAll(res1, res2); // <- No error, just doesn't wait. 
    list.Add(res1.Result[0]);
}
Gabriel
  • 157
  • 1
  • 2
  • 7
  • What does the definition of GetAIDBAPI look like? – BoltClock Apr 28 '16 at 04:23
  • Definition included. – Gabriel Apr 28 '16 at 04:28
  • Where is your calling code located? If it's in Main, it won't work correctly. If it's in its own async method, how is the task for that method being started? – BoltClock Apr 28 '16 at 04:37
  • @BoltClock Why would it not work correctly if it's in Main? – gnalck Apr 28 '16 at 04:38
  • 2
    @gnalck: It depends on how it's being called. See http://blog.stephencleary.com/2012/02/async-console-programs.html – BoltClock Apr 28 '16 at 04:40
  • The function GetAIDBAPI() is called inside IncluiValores(). – Gabriel Apr 28 '16 at 04:43
  • 1
    You're calling IncluiValores sychronously within Main (at least, that's what it seems), which is why your code is running synchronously. You can't have an async Main method, but you can work around that with a separate async context. See the link above, and the duplicate question: http://stackoverflow.com/questions/9208921/async-on-main-method-of-console-app – BoltClock Apr 28 '16 at 04:48
  • @BoltClock: I created a separate async context as sugested but the same problem persists. In other parts of the code where I use "await" async works fine. The problem appears to be restricted to WhenAll. – Gabriel Apr 28 '16 at 08:00

2 Answers2

11

You're calling an async void method, which inherently means you have no way of awaiting the result. Whenever you omit await, you're breaking the synchronization chain. The operation happens truly asynchronously, rather than "resynchronizing" through the await. The control is returned to the caller, while (sometime in the future) the operation resumes asynchronously.

Remember, await is a return. It's only the consistent use of await that gives you the synchronization. Stop using async void - change it to async Task and make sure you await the result properly. The same goes for your MainAsync method. Task is the void of async methods.

There's only one case where you should ever see async void, and that's within a synchronization context of a legacy framework's event handlers (e.g. in Winforms). If it's possible for an async method to return a Task, it really, really should. Don't break the chain.

Luaan
  • 62,244
  • 7
  • 97
  • 116
5

The error is that your main function does not await for completion of procedure IncluiValores. Your main program finishes before procedure IncluiValores is finished.

Because of this error I assume you still have some trouble understanding what happens when you use async-await.

Someone here on StackOverflow (alas I can't find it anymore), explained it to me using the following metaphor.

ADDITION: I found the metaphore
It is in this interview with Eric Lippert
Search somewhere in the middle for async-await
End Adition

Suppose you need to make breakfast. You want to toast some bread, boil some eggs and make some tea.

Synchronous

  • Put bread in the toaster and wait until the bread is toasted
  • Remove the bread from the toaster.
  • Start boiling water, wait until the water boils
  • Put some eggs in the boiling water and wait 7 minutes until your eggs are ready
  • Remove the eggs from the water
  • Start boiling water for your tea and wait until the water boils
  • When the water boils you put it in the teapot and add some tea leaves and wait 4 minutes
  • Finally you get everything together and bring it to your breakfast table.

You see you do a lot of waiting which is a waste of time, not to mention that your bread is probably cold by the time the tea is finished.

It would be much more efficient if you didn't wait all the time, but would start things simultaneously

using async-await: asynchronous using one thread

  • Start as in the synchronous case: Put bread in the toaster
  • but now you don't wait until the bread is toasted. Remember what you should do when the bread is toasted (remember this as Task A)
  • Start boiling water, but do not wait for the water to boil. Remember what you should do when the water boils (remember this as Task B)
  • Start boiling water for your tea, but do not wait for the waiter to boil. Remember what you should do when the tea kettle boils (remember this as Task C)

  • Wait until any of the Tasks A / B / C is finished. Continue what you remembered what you should do when the task was finished. If this needs some other waiting (time for the eggs or the tea to be ready), do not wait for it, but remember it as Tasks D and E and start waiting for all not finished tasks.

Note that in this method there is still only one person doing all the stuff. If you use async-await this way, there is only one thread involved. This thread is only waiting if it really has nothing to do. The advantage of this is that you don't face the problems you normally encounter when using several threads.

Asynchronous using several threads

You could hire several cooks: one to toast bread and one to boil eggs while you make the tead. This is an expensive method: start several threads, while the threads are doing nothing but wait most of the time. You also have the problems that the three cooks have to synchronize to make sure that they don't use the one-fire stove at the same time.

Stephen Cleary wrote a comprehensive article that describes this async-await behaviour in Async and Await (Thank you Stephen!)

static void Main(string[] args)
{
    var breakFast = await Task.Run( () => MakeBreakFast());
    // once here I know breakfast is ready
    Eat(breakFast);
}
private static async Task<BreakFast> MakeBreakFast()
{
    var taskToastBread = ToastBreadAsync();
    // do not await. As soon as the procedure awaits come back to do the next statement:
    var taskBoilEggs = BoilEggsAsync();
    // again do not await. Come back as the procedure awaits
    var  taskMakeTea = MakeTeaAsync();
    // do not wait, but come bask as soon as the procedure await

    // now wait until all three tasks are finished:
    await Task.WhenAll (new Task[] {taskToasBread, taskBoilEggs, taskMakeTea});
    // if here: all tasks are finished. Property Result contains the return value of the Task:
    return new BreakFast()
    {
        Toast = taskToastBread.Result,
        Eggs = taskBoilEggs.Result,
        Tea = taksMakeTea.Result,
    }
}

private static Task<Toast> ToastBreadAsync()
{
    var sliceOfBread = Loaf.CutSliceOfBread();
    Toaster.Insert(sliceOfBread);
    await Toaster.Toast();
    // the function does not wait but return to the caller.
    // the next is done when the caller await and the toaster is ready toasting
    var toast = Toaster.Remove();
    return Toast();
}

private static Task<Eggs> BoilEggsAsync()
{
    var eggPan = ...
    await eggPan.BoilWater();
    var eggs = Fridge.ExtreactEggs();
    EggPan.Insert(eggs);
    await Task.Delay(TimeSpan.FromMinutes(7));
    return EggPan.Remove();
}

You'll probably know by now how to make tea.

Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116
  • I have read this copy-pasted "making breakfast" answer so many times by now, I feel I know absolutely *everything* there is to know about toasting bread and boiling eggs (in parallel of course). – Kirill Shlenskiy Apr 28 '16 at 13:10
  • Similar questions lead to similar answers. I see quite often people struggling with the concept of async-await. Apparently the old answers aren't clear enough or they can't be found. Although I wasn't the first one to whom it was told, it really helped me understanding what happened. By the way: have you seen that the example improves? – Harald Coppoolse Apr 29 '16 at 06:23