TheTufik
TheTufik

Reputation: 105

.NET async\await fundamentals

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.

Upvotes: 5

Views: 5063

Answers (4)

Paulo Morgado
Paulo Morgado

Reputation: 14856

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.

Upvotes: 3

svick
svick

Reputation: 245036

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.

Upvotes: 2

noseratio
noseratio

Reputation: 61744

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).

Upvotes: 2

Reed Copsey
Reed Copsey

Reputation: 564871

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

Upvotes: 8

Related Questions