1

i feel pretty confused by the output i'm getting out of what i believe is purely async program. As you can observe there are no obvious anti patterns (i hope) and blocking calls.

slowURL throttles server response for 10 seconds. I did confirm by running calls to the local server with 10 second timeout that FetchSlowAsync method call effectively blocks the main thread for 10 seconds when running the code in console.

I expected that TaskScheduler would schedule the calls not in a sequential manner but by always randomly determining the method call order. Alas the output is always deterministic.

FetchSlowAsync start FetchSlowAsync got data! FetchAsync start FetchAsync got data! FetchBingAsync start FetchBingAsync got data! All done! 

My question is: what prompts FetchSlowAsync to block rather than TaskScheduler to perform a context switch to another async method and get back to it when it's done?

And the next question that follows the former: why all the methods in async Main are executed in the same order as they are being called given the async execution model is concurrent?

using static System.Console; using System.Net.Http; using System.Threading.Tasks; class Start { const string serviceURL = "https://google.com"; const string slowDown = "http://slowwly.robertomurray.co.uk/delay/10000/url/"; const string slowURL = slowDown + serviceURL; const string OkURL = serviceURL; static async Task FetchSlowAsync() { await Console.Out.WriteLineAsync("FetchSlowAsync start"); await new HttpClient().GetStringAsync(slowURL); //BLOCKS MAIN THREAD FOR 10 seconds await Console.Out.WriteLineAsync("FetchSlowAsync got data!"); } static async Task FetchAsync() { await Console.Out.WriteLineAsync("FetchAsync start"); await new HttpClient().GetStringAsync(OkURL); await Console.Out.WriteLineAsync("FetchAsync got data!"); } static async Task FetchBingAsync() { await Console.Out.WriteLineAsync("FetchBingAsync start"); await new HttpClient().GetStringAsync("https://bing.com"); await Console.Out.WriteLineAsync("FetchBingAsync got data!"); } static async Task Main() { await FetchSlowAsync(); await FetchBingAsync(); await FetchAsync(); await System.Console.Out.WriteLineAsync("All done!"); } } 
11
  • Would you mind if I refactored your code to not include local methods? There's no need for them here, and they're just added complexity. Commented May 25, 2019 at 18:05
  • Next, it sounds like you're expecting these to happen in parallel. Why? You're always awaiting whatever asynchronous task you've just started. You're awaiting the task returned by FetchSlowAsync() before you call FetchBingAsync(), for example. Commented May 25, 2019 at 18:07
  • (I can write that up in more detail as an answer, if you like. But it really depends on whether/why you expected things to happen in parallel.) Commented May 25, 2019 at 18:09
  • @JonSkeet sure Jon, i don't mind. That snippet was added for demo purposes only since my expectations of execution heavily differ from the output. Commented May 25, 2019 at 18:09
  • 1
    Well, perhaps you should read the documentation on async, await and TPL in general. From the docs "The await operator is applied to a task in an asynchronous method to insert a suspension point in the execution of the method until the awaited task completes" Commented May 25, 2019 at 18:43

1 Answer 1

8

Currently, you're waiting for the task returned from FetchSlowAsync() to complete before moving on to call FetchBingAsync etc. You're doing that by awaiting the tasks, here:

await FetchSlowAsync(); await FetchBingAsync(); await FetchAsync(); 

If you don't want to wait for the slow fetch to complete before you start the Bing fetch, etc, you can remember the tasks returned by them, and await them later:

static async Task Main() { // Start all three tasks Task t1 = FetchSlowAsync(); Task t2 = FetchBingAsync(); Task t3 = FetchAsync(); // Then wait for them all to finish await Task.WhenAll(t1, t2, t3); await Console.Out.WriteLineAsync("All done!"); } 

Now you get the kind of output I think you're expecting. For example, here's one run on my machine:

FetchSlowAsync start FetchBingAsync start FetchAsync start FetchAsync got data! FetchBingAsync got data! FetchSlowAsync got data! All done! 
Sign up to request clarification or add additional context in comments.

2 Comments

i guess i was expecting a bit more from C#'s async than it can actually give. I thought that if await exceeds IL instruction or time threshold it switches to the main thread and it will pick up the next async method to execute (in case of my code).
@alexeyhaidamaka: I would say that's not "expecting more" but "expecting very different" - and I think it would be much worse, by being far less predictable. If an await operator could just get bored and proceed with the next statement, what would you expect it to do if you were using the result of the await operator? It's really important to understand what async/await actually do, because then you can decide on the behavior you want.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.