5

I have questions regarding the execution order of async jobs.

I will ask my question with example because it is easier to be understandable.

It is an official example from https://msdn.microsoft.com/en-us/library/mt674882.aspx with some twist.

async Task<int> AccessTheWebAsync()
{ 
    HttpClient client = new HttpClient();

    //async operation:
    Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");

    // You can do work here that doesn't rely on the string from GetStringAsync.
    DoWork1();

    // The await operator suspends AccessTheWebAsync.
    string urlContents = await getStringTask;

    DoWork2();

    return urlContents.Length;
}

Can I say DoWork2 is a callback of client.GetStringAsync?

If so, DoWork2 is not immediately executed following the completion of client.GetStringAsync IF DoWork1 runs longer time than client.GetStringAsync.

Am I right here?

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
derek
  • 9,358
  • 11
  • 53
  • 94
  • Yes you start the task, when `DoWork1()` is completed, you will await the task. If it's already been completed, `DoWork2` starts immediately. If not, `DoWork2` starts when the task finished. – Alexander Derck Sep 02 '16 at 07:03
  • @AlexanderDerck Then it is NOT the kind of "callback" I expected. In my opinion, a callback should be immediately executed once its async function is done. – derek Sep 02 '16 at 07:11
  • 1
    Just to clarify: you want DoWork2 to execute (on completion of getStringTask) even if DoWork1 hasn't finished yet? Maybe look into the [Task.ContinueWith](https://msdn.microsoft.com/en-us/library/system.threading.tasks.task.continuewith.aspx) method. – Hans Kesting Sep 02 '16 at 07:22
  • And that's why this isn't generally referred to as a "callback". It's related, and under the covers may make use of a callback, but you won't generally find `await` described as a callback. – Damien_The_Unbeliever Sep 02 '16 at 07:50
  • @HansKesting Yes, I want `DoWork2` to be executed immediately upon the complition of getStringTask. It seems "await" and "Task.ContinueWith" functions the same. See this post:http://stackoverflow.com/questions/8767218/is-async-await-keyword-equivalent-to-a-continuewith-lambda – derek Sep 02 '16 at 21:35

3 Answers3

6

DoWork2 is not immediately executed following the completion of client.GetStringAsync if DoWork1 runs longer time than client.GetStringAsync

Once you hit that point await client.GetStrinkAsync, DoWork1() has already completed, as from your example it looks as it's execution is synchronous. Once client.GetStringAsync completes, then DoWork2 is set to execute.

The execution flow will be:

  1. GetStringAsync asynchronously starts. It is executed until it hits it's first internal await, and then yields control back to AccessTheWebAsync.
  2. DoWork1() kicks off. As it's synchronous, everyone waits for it's completion
  3. client.GetStringAsync is asynchronously waited for completion. await will naturally yield the control of execution to it's caller.
  4. Once client.GetStringAsync completes, execution will return to AccessTheWebAsync and DoWork2() will execute synchronously.
  5. urlContents.length will be returned.

Generally, everything after the first await keyword is set to be the continuation for the rest of the method.

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • Is there a way to make the `DoWork2` immediately executed once `client.GetStringAsync` is finished? – derek Sep 02 '16 at 07:16
  • @derek But that is exactly what happens. Once `client.GetStringAsync` completes, `DoWork2` will execute. – Yuval Itzchakov Sep 02 '16 at 07:17
  • Saying `DoWork1()` takes 10 seconds to finish while `client.GetStringAsync` takes 5 seconds. So when `client.GetStringAsync` finishes, `DoWork1()` is still running so we need wait for `DoWork1()` to complete, right? Then 5 seconds later, `DoWork1()` finishes. Only at this time, `DoWork2()` can start to work. Correct me if I am wrong. – derek Sep 02 '16 at 07:22
  • @derek No. `DoWork1()` is **synchronous**. Nothing will run until it completes. If `GetStringAsync` takes 5 seconds, then once the method finishes `DoWork1`, `GetStringAsync` will already be completed and execution will synchronously continue to `DoWork2`. – Yuval Itzchakov Sep 02 '16 at 07:24
  • Isn't `GetStringAsync` running with `DoWork1()` at the same time in different threads for the first 5 seconds? – derek Sep 02 '16 at 07:30
  • @derek Yes, you're correct, I forgot that `GetStringAsync` is executed without being awaited. We'll have to wait for `DoWork1` to complete before the `await GetStringAsync` hits. – Yuval Itzchakov Sep 02 '16 at 07:34
5

Yuval's answer is correct.

To answer your followup question:

Is there a way to make the DoWork2 immediately executed once client.GetStringAsync is finished?

Possibly. It depends on the context of this code. It's not possible to have DoWork1 and DoWork2 both running on the UI thread at the same time (obviously, a single thread can only do one thing at a time). But if this is called from a thread pool context, then sure, it's possible to run them both simultaneously.

However, you should shift your thinking from "callback" to "operation". That is, don't think of it as DoWork2 being a "callback" of GetStringAsync. Instead, think of it like this: I have an operation GetStringAsync, and I have an operation DoWork2; I need a new operation that does one and then the other.

Once you shift your thinking this way, the solution is to write a method for the new operation:

async Task<string> GetStringAndDoWork2Async()
{
  HttpClient client = new HttpClient();
  var result = await client.GetStringAsync("http://msdn.microsoft.com");
  DoWork2();
  return result;
}

Now you can use that new operation in your higher-level method:

async Task<int> AccessTheWebAsync()
{
  var task = GetStringAndDoWork2Async();

  DoWork1();

  string urlContents = await task;

  return urlContents.Length;
}
Community
  • 1
  • 1
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
-1

In:

//async operation:
Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");

you are creating a Task that runs an asynchronous operation and returns immediately to the caller, next DoWork1() execute synchronously, once done string urlContents = await getStringTask; suspends the continuation of the method until the awaited task has completed; once completed the program continues with DoWork2().

Ahmad
  • 1,462
  • 15
  • 23
  • 1
    Saying `getStringTask` blocks is exactly the opposite of what happens when you properly `await` an async method. – Yuval Itzchakov Sep 02 '16 at 07:25
  • 1
    Agreed: 'suspend' or 'suspend the execution' or assign the remaining of AccessTheWebAsync() as a continuation of 'getStringTask' is a more correct usage. Block is incorrect (updated) – Ahmad Sep 02 '16 at 07:47
  • 1
    No, the task does run immediately. All that `await` does is take a Task (or other awaitable) that some other piece of code has already started and it either does nothing (if the task is already complete) or pauses execution of the current method until the task is complete. But, just to re-emphasise, `await` doesn't **start** anything. – Damien_The_Unbeliever Sep 02 '16 at 07:48