2

I've commented on Eric Lippert's answer to What's the difference between Task.Start/Wait and Async/Await?

I am asking this question since I am still unsure if I understand the concept correctly and how I'd achieve my goal. Adding tons of comments is not very helpful for anyone.

What I understand: await tells the compiler that the current thread has the capacity to perform some other computation and get back once the awaited operation is done. That means the workflow is interrupted until the awaited operation is done. This doesn't speed up the computation of the context which contains the await but increases the overall application performance due to better usage of workers.

No my issue: I'd like to continue the workflow and in the end make sure the operation is done. So basically allow the worker to continue the current workflow even if the awaitable operation is not complete and await completion at the end of the workflow. I'd like the worker to spend time on my workflow and not run away and help someone else.

What I think might work: Consider n async Add operations and a Flush operation which processes added items. Flush requires the items to be added. But adding items doesn't require the previous item to be added. So basically I'd like to collect all running Add operations and await all of them once all have been added. And after they have been awaited they should be Flushed.

  1. Can I do this by adding the Add Tasks to a list and in the end await all those tasks?
  2. Or is this pseudo-async and has no benefit in the end?
  3. Is it the same as awaiting all the Add operations directly? (Without collecting them)
Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Noel Widmer
  • 4,444
  • 9
  • 45
  • 69
  • 3
    awaiting all Add operations directly will result in sequential adding, which you want to avoid. So option 1 is the way to go (and it will not lose benefits of using async-await). – Evk Dec 21 '17 at 11:47

2 Answers2

8

What I understand: await tells the compiler that the current thread has the capacity to perform some other computation and get back once the awaited operation is done.

That's pretty close. A better way to characterize it is: await means suspend this workflow until the awaited task is complete. If the workflow is suspended because the task isn't done, that frees up this thread to find more work to do, and the workflow will be scheduled to resume at some point in the future when the task is done. The choice of what to do while waiting is given to the code that most recently called this workflow; that is, an await is actually a fancy return. After all, return means "let my caller decide what to do next".

If the task is done at the point of the await then the workflow simply continues normally.

Await is an asynchronous wait. It waits for a task to be done, but it keeps busy while it is waiting.

I'd like to continue the workflow and in the end make sure the operation is done. So basically allow the worker to continue the current workflow even if the awaitable operation is not complete and await completion at the end of the workflow. I'd like the worker to spend time on my workflow and not run away and help someone else.

Sure, that's fine. Don't await a task until the last possible moment, when you need the task to be complete before the workflow can continue. That's a best practice.

However: if your workflow is doing operations that are taking more than let's say 30 milliseconds without awaiting something, and you're on the UI thread, then you are potentially freezing the UI and irritating the user.

Can I do this by adding the Add Tasks to a list and in the end await all those tasks?

Of course you can; that's a good idea. Use the WhenAll combinator to easily await all of a sequence of tasks.

Is it the same as awaiting all the Add operations directly? (Without collecting them)

No, it's different. As you correctly note, awaiting each Add operation will ensure that no Add starts until the previous one completes. If there's no requirement that they be serialized in this manner, you can make a more efficient workflow by starting the tasks first, and then awaiting them after they're all started.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
3

If I understand your question correctly, what you want to do is parallelize the asynchronous work, which is very common.

Consider the following code:

async Task Add(Item item) { ... }

async Task YourMethod()
{
    var tasks = new List<Task>();
    foreach (var item in collection)
    {
        tasks.Add(Add(item));
    }

    // do any work you need
    Console.WriteLine("Working...");

    // then ensure the tasks are done
    await Task.WhenAll(tasks);

    // and flush them out
    await Flush();
}
Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
  • Is `Task.WhenAll(tasks)` the same as `tasks.ForEach(async t => await t)`? – Noel Widmer Dec 21 '17 at 12:01
  • 2
    @NoelWidmer It is a little more than just that, look [here](http://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs,2529f60968787540). – Camilo Terevinto Dec 21 '17 at 12:04
  • 4
    A caveat is that it is possible for a `Task`-returning method to return a `Task` that isn't yet started, in which case you won't start the task running until the `WhenAll`. `async` methods always return a started `Task`, as will most other API methods that return a `Task` for something, but if it was created with the `Task` constructor you would want to call `Start()` on it during the initial creation loop. – Jon Hanna Dec 21 '17 at 15:31