ridiculous_fish
ridiculous_fish

Reputation: 18551

Does async really enable callers to await?

MSDN says:

If you specify that a method is an async method by using an Async or async modifier, you enable the following two capabilities... The marked async method can itself be awaited by methods that call it.

But another page gives this example:

private async Task SumPageSizesAsync()
{
    // To use the HttpClient type in desktop apps, you must include a using directive and add a 
    // reference for the System.Net.Http namespace.
    HttpClient client = new HttpClient();
    // . . .
    Task<byte[]> getContentsTask = client.GetByteArrayAsync(url);
    byte[] urlContents = await getContentsTask;

    // Equivalently, now that you see how it works, you can write the same thing in a single line.
    //byte[] urlContents = await client.GetByteArrayAsync(url);
    // . . .
}

Here we see it call a function GetByteArrayAsync which is not equipped with the async keyword, and yet the caller is able to await on the result.

  1. Is MSDN wrong when it says that the async modifier enables callers to await?
  2. From the caller's perspective, what's the difference between a function that returns Task<T>, and the same function marked as async?

Upvotes: 2

Views: 311

Answers (5)

Artem Koshelev
Artem Koshelev

Reputation: 10607

From async/await faq (emphasis mine):

What does the “async” keyword do when applied to a method?

When you mark a method with the “async” keyword, you’re really telling the compiler two things:

  1. You’re telling the compiler that you want to be able to use the “await” keyword inside the method (you can use the await keyword if and only if the method or lambda it’s in is marked as async). In doing so, you’re telling the compiler to compile the method using a state machine, such that the method will be able to suspend and then resume asynchronously at await points.
  2. You’re telling the compiler to “lift” the result of the method or any exceptions that may occur into the return type. For a method that returns Task or Task, this means that any returned value or exception that goes unhandled within the method is stored into the result task. For a method that returns void, this means that any exceptions are propagated to the caller’s context via whatever “SynchronizationContext” was current at the time of the method’s initial invocation.

So, think of it as a syntactic sugar over plain old asynchronous .NET model: more compiler checks, less code, and completely indifferent from the caller perspective (caller either awaits the result inside async method or uses other TPL primitives, or even blocks).

Actually, if you check the source code of the GetByteArrayAsync method, it is simply a wrapper over GetContentAsync method constructing the resulting Task with TaskCompletionSource and continuation-passing style.

private Task<T> GetContentAsync<T>(Uri requestUri, HttpCompletionOption completionOption, T defaultValue,
        Func<HttpContent, Task<T>> readAs)
    {
        TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();

        GetAsync(requestUri, completionOption).ContinueWithStandard(requestTask =>
        {
            if (HandleRequestFaultsAndCancelation(requestTask, tcs))
            {
                return;
            }
            HttpResponseMessage response = requestTask.Result;
            if (response.Content == null)
            {
                tcs.TrySetResult(defaultValue);
                return;
            }

            try
            {
                readAs(response.Content).ContinueWithStandard(contentTask =>
                {
                    if (!HttpUtilities.HandleFaultsAndCancelation(contentTask, tcs))
                    {
                        tcs.TrySetResult(contentTask.Result);
                    }
                });
            }
            catch (Exception ex)
            {
                tcs.TrySetException(ex);
            }
        });

        return tcs.Task;
    }

Upvotes: 2

ispiro
ispiro

Reputation: 27673

As far as I can tell it's not being awaited. The Task is.

The marked async method can itself be awaited - that's for methods. A Task doesn't need that in order to be awaitable (which is what you're doing in await getContentsTask).

This also answers part 2 - The difference is that the one that returns Task and is not marked async cannot be awaited. Which is what MSDN says in the quoted text.

(I might be wrong, of course.)

Upvotes: -1

i3arnon
i3arnon

Reputation: 116548

Is MSDN wrong when it says that the async modifier enables callers to await?

No. It does allow callers to await as it rewrites your method to return a Task/Task<T>. But it isn't the only way to do so. Any method that returns an awaitable (e.g. Task<bool>, YieldAwaitable) enables the callers to await.

From the caller's perspective, what's the difference between a function that returns Task<T>, and the same function marked as async?

If it's implemented correctly, there shouldn't be any difference. The async and await keywords are a way for the compiler to help you write asynchronous code. But they are not necessary. You can (and you always could have) write asynchronous code without them.

Take Task.Delay for example. It creates a task, sets a timer, configures the timer to complete the task after some time and returns the task. It doesn't use async-await and it doesn't need to. But by returning a task it allows the caller to await it.

In fact, a lot of the task-returning methods in the .NET framework don't use async-await internally as they are the "root" asynchronous methods. They return a task but don't have a task to await themselves.

Upvotes: 4

Daniel Mann
Daniel Mann

Reputation: 59020

An async modifier allows you to use the await keyword within that method. Any Task can be awaited within a method marked async -- the Task represents an ongoing activity that may or may not already be complete when it is returned to the caller.

Upvotes: 1

Scott Chamberlain
Scott Chamberlain

Reputation: 127563

Do do not await a function, you await a Task or a Task<T> that a function returns1. In any examples where you see await client.GetByteArrayAsync(url) there is a hidden implicit Task<byte[]> that gets "passed" between the client.GetByteArrayAsync(url) and the await.

You question is similar to asking "How does the + work when you do

int result = 1 + 2;

vs when you do

int two = 2;
int result = 1 + two;

The documentation states it is for adding two numbers together, but I am not adding two numbers in the 2nd example I am adding a number and a variable."


1: It is a little more completed than that, but for 99% of the time just think of it that way.

Upvotes: 3

Related Questions