简体   繁体   中英

What happens when you await a Task before returning it?

I am experimenting with the new async and await keywords. I produced the following asynchronous function:

private async static Task<string> GetStringAsync(string pageAddress)
{
    HttpClient client = new HttpClient();
    return client.GetStringAsync(pageAddress);
}

I understand that I am returning a Task<String> and can await the result from another method. This method works fine. My question is what happens (under the hood as it were) when I replace the second line of the above function with the following (notice the introduction of the await keyword):

return await client.GetStringAsync(pageAddress);

The function behaves in exactly the same way! Remember the function returns Task<string> not string . Is the await keyword here degenerate? Does the compiler simply strip it from my code?

The answer to this question is too large to post here given your likely current level of understanding. What you should do is start by reading my MSDN article and then Mads' MSDN article; they are a good beginner introduction to the feature and Mads describes how it is implemented. You can find links here:

http://blogs.msdn.com/b/ericlippert/archive/2011/10/03/async-articles.aspx

Then if you are interested in the theory underlying the feature you should start by reading all my articles on continuation passing style:

http://blogs.msdn.com/b/ericlippert/archive/tags/continuation+passing+style/

Start from the bottom. Once you understand the notion of continuation , you can then read my series of articles on how we designed the async feature:

http://blogs.msdn.com/b/ericlippert/archive/tags/async/

As Eric Lippert pointed out, the first version won't compile; you have to remove the async keyword or you'll get a type error.

Here's a useful mental model regarding how the async and await keywords work with the return type:

  • Any value T returned by an async method is "wrapped" into a Task<T> .
  • The await keyword (which you can think of as an operator), when applied to a Task<T> , will "unwrap" it, resulting in a value of type T .

Now, that's an extreme simplification; what's actually happening is more complicated. Eg, this simplification skips over how await works with the current SynchronizationContext : in the second example, the method will attempt to return to the original context after the await completes, so you will observe different behavior if that context is busy.

But for the most part, the two examples are almost equivalent. The second one is less efficient due to the async state machine and resuming on the context.

I have an async / await intro that you may find helpful; in that post I try to explain async in a way that is not too complex but also not actually incorrect . :)

Eric's obviously the expert here and his advice is sound, but to answer your specific question:

In the first version, the async keyword on the method is irrelevant and your GetStringAsync method returns the same Task<string> awaitable that's returned by client.GetStringAsync .

In the second version, the async keyword on the method is required because you're using await in the method and the await keyword creates and returns a separate Task<string> awaitable that completes once the awaitable from client.GetStringAsync completes. When that occurs, the await then evaluates to the string that was asynchronously obtained by client.GetStringAsync which is returned as the result of your asynchronous method.

So to the caller of GetStringAsync , they're functionally the same, but the first version is cleaner.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM