1

Picture the following code:

var client = new HttpClient();
var response = await client.GetAsync("www.someaddress.yo");
string content = await response.Content.ReadAsStringAsync();

Is there any added benefit, other than possibly saving a single thread, by writing the above code the following way:

 var client = new HttpClient();
 string content = await client.GetAsync("www.someaddress.yo")
       .ContinueWith(r => r.Result.Content.ReadAsStringAsync()).Result;

Correct me if I'm wrong, but I believe performance-wise both codes end up doing the same amount of work.

SpiritBob
  • 2,355
  • 3
  • 24
  • 62
  • @JohnB could you add some more insight as to why that is? – SpiritBob Oct 04 '19 at 06:31
  • 2
    its not just a code smell, it can cause blocking to happen. – Tim Rutter Oct 04 '19 at 06:31
  • await asynchronously unwraps the result of your task, whereas just using Result would block until the task had completed – jazb Oct 04 '19 at 06:31
  • 2
    Here is an excellent article explains it well https://montemagno.com/c-sharp-developers-stop-calling-dot-result/ – jazb Oct 04 '19 at 06:33
  • 1
    @JohnB The canonical source is https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html – GSerg Oct 04 '19 at 06:36
  • 1
    Possible duplicate of [Is Async await keyword equivalent to a ContinueWith lambda?](https://stackoverflow.com/q/8767218/11683) – GSerg Oct 04 '19 at 06:37
  • @GSerg - nice - noted :-) – jazb Oct 04 '19 at 06:37
  • @JohnB I'm confused - doesn't ContinueWith reflect itself once the task has completed execution, such as Result would no longer pose danger for blocking? – SpiritBob Oct 04 '19 at 06:38
  • 3
    Just dropping a very good talk about async/ await and correcting common mistakes with it: https://www.youtube.com/watch?v=J0mcYVxJEl0 – MindSwipe Oct 04 '19 at 06:38
  • I thought `ContinueWith` was a chaining technique - the `Result` bad boy is still going to do his blocking move – jazb Oct 04 '19 at 06:39
  • 1
    article on the diff between `continuewith` and `await` here: https://www.c-sharpcorner.com/UploadFile/pranayamr/difference-between-await-and-continuewith-keyword-in-C-Sharp/ – jazb Oct 04 '19 at 06:42
  • 4
    @SpiritBob you're right in that `r.Result` won't block, but the `.Result` at the end of the last line is still going to block. – Sweeper Oct 04 '19 at 06:42
  • @Sweeper I'm guessing there's no avoiding it? Thank you all for the valuable reads! – SpiritBob Oct 04 '19 at 06:43
  • 2
    "saving a single thread" - I see no threads. Nothing in your first sample is going to create threads. Since it's not been linked yet that I can see - [There is no thread](https://blog.stephencleary.com/2013/11/there-is-no-thread.html) – Damien_The_Unbeliever Oct 04 '19 at 07:14
  • @Damien_The_Unbeliever I'm not sure to the technicalities, but I believe a task is composed of workers called threads? I've edited my response above with the "possible" keyword and I read somewhere that using ContinueWith would pass the thread in the previous task to do the new task, effectively (possibly) saving a thread? – SpiritBob Oct 04 '19 at 07:37
  • 1
    @SpiritBob no, a task is just a promise that *something* will complete in the future. That something *may* get executed on a threadpool thread, or not. A task returned by a TaskCompletionSource for example doesn't run on any threads. In any case, `now` was back in 2012 when `async/await` were introduced. Classes like HttpClient are *meant* to be used with `async/await`. In fact, using `ContinueWith` like this can allocate objects without providing any benefit. It sure makes expection handling harder though – Panagiotis Kanavos Oct 04 '19 at 07:43

1 Answers1

3

The second snippet has no benefits, doesn't "save" any threads while allocating another task object and making debugging and exception handling harder by wrapping any exceptions in an AggregateException.

A task is a promise that something will produce some output in the future. That something may be :

  • A background operation running on a threadpool thread
  • A network/IO operation that doesn't run on any thread, waiting instead for the Network/IO driver to signal that the IO has finished.
  • A timer that signals a task after an interval. No kind of execution here.
  • A TaskCompletionSource that gets signalled after some time. No execution here either

HttpClient.GetAsync, HttpClient.GetStringAsync or Content.ReadAsStringAsync are such IO operations.

await doesn't make anything run asynchronously. It only awaits already executing tasks to complete without blocking.

Nothing is gained by using ContinueWith the way the second snippet does. This code simply allocates another task to wrap the task returned by ReadAsStringAsync. .Result returns the original task.

Should that method fail though, .Result will throw an AggregateException containing the original exception - or is it an AggregateException containing an AggregateException containing the original? I don't want to find out. Might as well have used Unwrap(). Finally, everything is still awaited.

The only difference is that the first snippet returns in the original synchronization context after each await. In a desktop application, that would be the UI. In many cases you want that - this allows you to update the UI with the response without any kind of marshalling. You can just write

var response = await client.GetAsync("www.someaddress.yo");
string content = await response.Content.ReadAsStringAsync();
textBox1.Text=content;

In other cases you may not want that, eg a library writer doesn't want the library to affect the client application. That's where ConfigureAwait(false) comes in :

var response = await client.GetAsync("www.someaddress.yo").ConfigureAwait(false);
string content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236