Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Task.WhenAll not waiting

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]);
}
like image 532
Gabriel Avatar asked Apr 28 '16 04:04

Gabriel


People also ask

What is the use of Task waitall in Java?

The Task.WaitAll blocks the current thread until all other tasks have completed execution. The Task.WhenAll method is used to create a task that will complete if and only if all the other tasks have completed. So, if you are using Task.WhenAll you will get a task object that isn’t complete.

Whenall task is executed before the other tasks are completed?

In the 1st example, we could see that when using Task.WhenAll the task complete is executed before the other tasks are completed. This means that Task.WhenAll doesn’t block the execution.

What are the tasks to wait on?

The tasks to wait on for completion. A task that represents the completion of all of the supplied tasks. The tasks argument was null. The tasks collection contained a null task.

How to run Multiple async tasks and waiting for all to complete?

How to run multiple async tasks and waiting for them all to complete in C#? The Task.WaitAll blocks the current thread until all other tasks have completed execution. The Task.WhenAll method is used to create a task that will complete if and only if all the other tasks have complete.


2 Answers

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.

like image 163
Luaan Avatar answered Sep 19 '22 07:09

Luaan


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.

like image 42
Harald Coppoolse Avatar answered Sep 18 '22 07:09

Harald Coppoolse