5

After playing with .NET 4.5 async\await framework I have a question.

Let's look at this program (the msdn example):

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

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

        DoIndependentWork();

        string urlContents = await getStringTask;

        return urlContents.Length;
    }


    void DoIndependentWork()
    {
        resultsTextBox.Text += "Working . . . . . . .\r\n";
    }

The program will run in this order:

  1. new HttpClient.
  2. GetStringAsync invoke synchronously.
  3. In some point, GetStringAsync calls await and the control return to AccessTheWebAsync.
  4. DoIndependentWork invoked.
  5. The program waits the string to be returned (blocks if the operation didn't complete).
  6. return the length of the urlContent.

One thing that took me a while to understand is that the method GetStringAsync runs synchronously despite its name (the name convention is really misleading).

In oreder to run the method asynchronously, we need to use Task.Run or Task.Factory.StartNew explicitly.

But the real question is, if we have independent work, why not do it right away rather then waiting the await to be called from GetStringAsync? (In other words, why async methods doesn't run asynchronously by definition?)

EDIT: I'll rephrase the second and third operations:

(2) GetStringAsync start synchronously.

(3) In some point, GetStringAsync calls await and the thread forks, the control return to AccessTheWebAsync.

4 Answers 4

8

One thing that took me a while to understand is that the method GetStringAsync runs synchronously despite its name (the name convention is really misleading).

That is incorrect. GetStringAsync returns a Task<string>. It will return immediately, which means DoIndependentWork will (potentially) run before the download has completed. The await operator will asynchronously wait until the Task<T> returned by GetStringAsync is done.

But the real question is, if we have independent work, why not do it right away rather then waiting the await to be called from GetStringAsync? (In other words, why async methods doesn't run asynchronously by definition?)

The actual "work" of the asynchronous methods is running after the method has returned. The Task that's returned will (normally) be in a state that's not completed, which means the method is still running asynchronously. You can check Task.IsCompleted to verify.

Try this:

Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
Debug.WriteLine("Completed? {0}", getStringTask.IsCompleted); // Will likely print false
DoIndependentWork();
string urlContents = await getStringTask;
Debug.WriteLine("Completed now? {0}", getStringTask.IsCompleted); // Will always print true
Sign up to request clarification or add additional context in comments.

7 Comments

GetStringAsync() doesn't have to return immediately, everything in it before the first await will execute synchronously, as the question implied (assuming it's implemented using await).
@svick Yes, though the question implies that the OP is assuming it's fully synchronous. It's potentially partially synchronous, but not fully synchronous. HttpClient isn't going to block the entire time, though...
I edited the question, and as I ask @sivck, why the architects of this "async framework" decided to let the start be synchronously and not all the way asynchronously? (let's put GUI stuff aside)
@TheTufik The implementation required for force asynchrony would actually be far less efficient in many cases. Part of the reason for having the methods run synchronous until they no longer can is that the context switching involved in asynchrony is (relatively) expensive. That's also why the first await doesn't necessarily cause asynchrony - if you await a Task that that's already completed, the framework/compiler will keep going synchronously for perf reasons.
Not waiting for task that already completed can be achieved in more dirty way with Task.ContinueWith. In the end, it's a trade off - the amount of time of context switching in cases that it wasn't needed versus the time of the small part of the method which run synchronously. I didn't analyzed this cases, but I think that fork immediately on await (and not forking when the task completed, the dirty way) will be more efficient then today's implementation.
|
3

Each async method is split into segments on each await. Each segment will become a state on a compiler generated state machine.

Each await instruction works on an awaitable for which a Task is the most common case.

Each state/segment will be executed synchronously until the where the received awaitable is checked if it is already completed.

If the awaitable is completed, it execution will continue on the next state.

If the awaitable is not completed and there isn't a current SynchronizationContext, the execution will be blocked until the the awaitable is completed at which time the execution of the next state will be started.

If a current SynchronizationContext exists, the execution will return to the caller and when the awaitable is completed the continuation to the next state will be posted to the captured SynchronizationContext.

Comments

2

The program waits the string to be returned (blocks if the operation didn't complete).

await doesn't block on await getStringTask. It will save the internal state of the AccessTheWebAsync method (local variables and where the execution point is) in a compiler-generated state machine object and will return to the outer method which called AccessTheWebAsync. The state will be restored and the execution will be resumed later asynchronously when getStringTask task has become completed, via a compiler-generated continuation callback. If you're familiar with C# iterators and yield keyword, the await state machine control flow is very similar to it, albeit iterators are executed synchronously.

How exactly the execution will be resumed after await depends on the synchronization context of the thread initiating the await. If it's a UI thread pumping Windows messages, the continuation callback will likely be called on the next iteration of the thread's message loop (typically inside Application.Run). If it's a non-UI thread (e.g., a console application), the continuation may happen on a different thread at all. So, while the control flow of your method remains logically linear, technically it is not (as it would have been if you just did getStringTask.Wait() instead of await getStringTask).

Comments

2

The program waits the string to be returned (blocks if the operation didn't complete).

No, it doesn't. await waits, but it doesn't block any threads. That's the whole point.

One thing that took me a while to understand is that the method GetStringAsync runs synchronously despite its name (the name convention is really misleading).

That's wrong. Most of the method does run asynchronously. It most likely also has a small synchronous part at the start, but that should be negligible.

In oreder to run the method asynchronously, we need to use Task.Run or Task.Factory.StartNew explicitly.

No, the method already does run asynchronously. If you want to run the small synchronous part on another thread, then you can use Task.Run(), but that almost never makes any sense. Also, don't use StartNew() for this, it doesn't work well with async methods.

In other words, why async methods doesn't run asynchronously by definition?

A significant point about await is that it resumes on the same context where it left.

This can be very useful in GUI applications, where you often want to modify the UI, then execute the async operation and then modify the UI again. If async automatically meant that the whole method executes on a ThreadPool thread, then that wouldn't be possible.

Also, doing it this way is more efficient, because you don't have to switch to another thread for something that takes very little time.

2 Comments

I rephrased the operation.
The thread will most likely to block if GetStringAsync didn't finish - In this program specific the caller is async void so it will be blocked. I think that in most application that is the same situation. And what's the difference between await with Task.ContinueWith with CurrentSynchronization? | It most likely also has a small synchronous part at the start, but that should be negligible.| - that's the my question.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.