0

I managed to find some answers to similar questions posted around the internet, but no one of them explained it satisfying enough to make me understand the difference in the code below.

I am aware that await, unlike .Result, doesn't block calling thread. But what if we're trying to access this property from a task, which doesn't block it anyway?

For instance, is there any difference between this

public static Task PrintPageAsync(string url)
{
    return Task.Run(() =>
    {
        WebRequest webRequest = WebRequest.Create(url);
        WebResponse response = webRequest.GetResponseAsync().Result;
        using (StreamReader reader = new StreamReader(response.GetResponseStream()))
        {
            string text = reader.ReadToEndAsync().Result;
            Console.WriteLine(text);
        }
    });
}

and this

public static async Task PrintPageAsync(string url)
{
    WebRequest webRequest = WebRequest.Create(url);
    WebResponse response = await webRequest.GetResponseAsync();
    using (StreamReader reader = new StreamReader(response.GetResponseStream()))
    {
        string text = await reader.ReadToEndAsync();
        Console.WriteLine(text);
    }
}
Hossein Golshani
  • 1,847
  • 5
  • 16
  • 27
QmlnR2F5
  • 934
  • 10
  • 17
  • Doesn't matter, the difference is still that it will block. Also mixing async with blocking like that can lead to code that will lock up. – juharr Oct 06 '18 at 16:39
  • That's interesting, what do you mean by "it will block"? I'm using it in Main method like this: PrintPageAsync(@"https://en.wikipedia.org/wiki/Instruction-level_parallelism"); while (true) { Console.Write("*"); } And it doesn't block no matter which one I use. It starts printing stars instantly, after some time it prints the page and is still printing stars after that. – QmlnR2F5 Oct 06 '18 at 16:53
  • That's because the first one is started up in another thread. You might as well call the synchronous `ReadToEnd` in that case. Remove the `Task.Run` and see what happens. – juharr Oct 06 '18 at 16:57
  • I think I understand, but my point is what's the difference between the two when I am using Task.Run, so it starts from another thread. Will it ever behave differently than the async method when calling it? – QmlnR2F5 Oct 06 '18 at 17:09
  • 1
    Ultimately the difference is how many threads are in use. For this simple example it's not a big difference, but if this was code on a server behind a web service call then you'd have a scaling problem when you get many requests and the server slows down because of a bunch of blocked threads. – juharr Oct 06 '18 at 17:18
  • So, what you're saying is, in terms of output they are the same, but async methods automatically do fancy things in the background which lead to improved performance? – QmlnR2F5 Oct 06 '18 at 17:34
  • More or less, though I'd guess the first one will output more stars before the text from the url because the main thread breaks off at the `Task.Run` instead of at the `await`. – juharr Oct 06 '18 at 17:37

2 Answers2

1

.Result will execute your code synchronously, i.e. you're disregarding the very essence of the Task and entire TPL standing behind it. await, as it says, is a marker for the compiler to rewrite your method in a good-old "callback" fashion (a-ka typical JavaScript), which is asynchronous way to complete exactly the same computation.

Simpler: you should prefer await over .Result whenever possible.

Zazaeil
  • 3,900
  • 2
  • 14
  • 31
  • Why does it matter? If we're using it inside Task's body, doesn't it block just inside the task, and calling method isn't effected? No matter which one I use they give me the same result. Eg. PrintPageAsync(@"https://en.wikipedia.org/wiki/Instruction-level_parallelism"); while (true){ Console.Write("*"); } Using this in Main method prints stars, prints page's content after some time and prints stars again. – QmlnR2F5 Oct 06 '18 at 17:01
  • I think you have to go deeper into what an asynchronicity is. It blocks until you either get a result of the underlying computation or it gets cancelled/failed with exception. Either way implies **sync** execution which is opposite to the `Task`'s purpose. – Zazaeil Oct 06 '18 at 17:05
  • I understand the difference, but what I think is since we're starting the task in another thread, it doesn't block the **calling thread** and when we're blocking with .Result, we're blocking **task's thread**, not **calling thread**. Am I completely wrong? Also, as I've mentioned, outputs of both methods (while using the code from my previous comment) are the same. If one of them is blocking, while another isn't, then why outputs are the same? – QmlnR2F5 Oct 06 '18 at 17:30
  • @pink.Overload your example is simply misusing of tasks. You don't wrap `Task` into `Task` unless there is absolute necessity to. Such a code is higly inefficient in terms of threads queue consumption. Indeed, in your exmaple **calling threade** (`Task.Run(...)`) is not blocked; created one - gets blocked. More than than, depending upon huge variety of factors, new thread might not be created, in which case you gonna block calling thread. Putting long story short, don't disobey `Task` (simple and straightforward) rules, otherwise one day it will hurt you in return. – Zazaeil Oct 06 '18 at 17:37
  • I get it. Could you just specify where in my code wrapping `Task` into `Task` occurs so I'll know what I should avoid? – QmlnR2F5 Oct 06 '18 at 17:50
  • `ReadToEndAsync()` returns `Task` I guess. You wrapped it into `Task.Run(...)` which was unnecessary. Same applies to the `.GetResponseAsync()`. – Zazaeil Oct 06 '18 at 17:56
0

Since the first example starts a new task on a threadpool thread, calling asynchronous methods is unnecessary, when there are equivalent synchronous methods. It does not have any benefit, it just needlessly add some overhead involved in managing asynchronous tasks. Just use .GetResponse() instead of .GetResponseAsync().Result and .ReadToEnd() instead of .ReadToEndAsync().Result.

await, unlike .Result, doesn't block calling thread

This is not always true. Awaited methods can execute (partially or completely) synchronously, if they decide to do so.

For instance, is there any difference?

There are a bunch of differences between the two examples. Although they may be insignificant in certain contexts, they can be crucial in another:

  1. AggregateException

    When exception occurs in a task or task is cancelled, calling Task.Result will wrap exception in an AggregateException. Contrary, awaiting this task will throw the original exception. So you must be careful when catching specific exception.

  2. Thread

    In the first example, whole method will execute on the same (threadpool) thread. The second example can execute on several different threads, depending on the current SynchronizationContext. Code sensitive to thread-affinity should be avoided.

  3. SynchronizationContext

    First exapmple will execute without SynchronizationContext, while the second will restore original SynchronizationContext after each await. For console applications, this doesn't matter. But in WPF or WinForms applications, UI elements can be accessed only from corresponding synchronization context.

  4. Asynchronous execution

    In the first example, PrintPageAsync will return immediatelly after new task is queued for execution, while the second will execute synchronously up to the first await (or possibly even after that). This can have severe impact on GUI responsivnes, especially when async method uses WebRequest, because GetResponseAsync() method performs DNS resolution synchronously (see How to create HttpWebRequest without interrupting async/await?). So wrapping code in a Task.Run() is recomended, when method that uses WebRequest is called from UI thread.

Community
  • 1
  • 1
Ňuf
  • 6,027
  • 2
  • 23
  • 26