Why doesn't this call to .Result provoke a deadlock?

Multi tool use
Multi tool use


Why doesn't this call to .Result provoke a deadlock?



I'm still trying to wrap my head around when I can use .Result, and when I can't. I have been trying to avoid it entirely, by using async "all the way down".



While doing some work in our ASP.NET web app (NOT core), I came across this code. Now, the code works (and has been working for 2 years), so I know it works, I just don't know why it works.



This is fired from a sync controller method. It is a chain of method calls that goes through a few layers until it finally does some HTTP work. I have simplified the code here:


// Class1
public byte GetDocument1(string url)
{
return class2.GetDocument2(url).Result;
}

// Class2
public Task<byte> GetDocument2(string url)
{
return class3.GetDocument3(url)
}

// Class3
public async Task<byte> GetDocument3(string url)
{
var client = GetHttpClient(url);
var resp = client.GetAsync(url).Result;

if(resp.StatusCode == HttpStatusCode.OK)
{
using(var httpStream = await resp.Content.ReadAsStreamAsync())
{
// we are also using
await httpStream.ReadAsync(...);
}
}
}



So as far as I can tell, when this all starts, I'm on the "main ASP sync context" (I start in a controller and eventually get to this code). We aren't using any .ConfigureAwait(false), so I believe we are always returning to this context.


.ConfigureAwait(false)


GetDocument3


client.GetAsync(url).Result


GetDocument3


.Result


await


.Result


GetDocument1


.Result





I'm still trying to wrap my head around when I can use .Result why? Why not just follow the advice: don't use it.
– mason
Jul 2 at 12:22


I'm still trying to wrap my head around when I can use .Result





@mason - c'mon man, you know that's just not always possible. In addition, I HATE not knowing stuff. .Result is real, and apparently legit, and I feel I should know how/when to use it.
– emery.noel
Jul 2 at 14:24






What do you mean "not always possible"? If you aren't in an async context, don't try to use async code. Which means you should always be able to await and should never need to use .Result. If you want to know when to use it, the answer is "never".
– mason
Jul 2 at 14:33


await


.Result





If the code you're invoking doesn't rely on the synchronization context, than it will not deadlock.
– Paulo Morgado
Jul 2 at 14:44




1 Answer
1



client.GetAsync(url).Result is synchronously blocking. resp.Content.ReadAsStreamAsync() is actually not doing any awaiting. The Task is already completed, because HttpCompletionOption.ResponseContentRead is used in GetAsync, so the entire block of code here is synchronous code pretending to be asynchronous.


client.GetAsync(url).Result


resp.Content.ReadAsStreamAsync()


Task


HttpCompletionOption.ResponseContentRead


GetAsync



In general, you should never use .Result or .Wait or Task.Wait* - if you absolutely have to, use GetAwaiter().GetResult(), which doesn't throw exceptions wrapped in AggregateExceptions, for which you may not have a catch, but even that should be avoided like the plague. You're right to use async all the way down.


.Result


.Wait


Task.Wait*


GetAwaiter().GetResult()


AggregateException


catch


async



The deadlock is only a problem in a context that wants to return back to the original thread (e.g. UI or ASP.NET), and that thread is blocked by a synchronous wait. If you always use ConfigureAwait(false) on every await, unless you know you actually need to preserve the context on the continuation (usually only at the top level UI event handler because you need to update something on the UI, or in the top level of the ASP.NET controller) then you should be safe.


ConfigureAwait(false)


await



Synchronous blocking inside asynchronous code also isn't good, but won't cause the deadlock. Using await with ConfigureAwait(true) (the default), inside a synchronous wait, in a context that wants to return back to a specific thread will cause the deadlock.


await


ConfigureAwait(true)





@georgeHeylar - re: 3rd paragraph. Ok but, I'm in ASP.NET. I'm still on that original context/thread. I am synchronously blocking on that GetAsync().Result, right? So if I'm blocking, and GetAsync() is going to continue on that blocked thread ... how does that work? Is it "ok" to use .Result BECAUSE GetAsync() is guaranteed to run on some other thread, or something like that?
– emery.noel
Jul 2 at 14:31


GetAsync().Result


GetAsync()


.Result


GetAsync()





Actually, GetAwaiter().GetResult() unwraps the AggregateException.
– Paulo Morgado
Jul 2 at 14:45


GetAwaiter().GetResult()


AggregateException





When you use .Result, it executes synchronously, blocking the thread and never leaving it, so it has no problem getting back on that thread because it never left. If you were using .Result on an outer Task and an await with .ConfigureAwait(true) on the inner Task in asp.net, the inner wouldn't be able to return to the thread that was currently blocking on the outer Task. Thanks Paulo, I didn't know that! Just not getting an unexpected AggregateException that probably has no catch is really what I care about when using it.
– George Helyar
Jul 2 at 17:13



.Result


.Result


Task


await


.ConfigureAwait(true)


Task


Task


AggregateException


catch





Thank you @GeorgeHeylar, it is becoming clearer!
– emery.noel
Jul 5 at 10:45






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

1e,3ar9k3,zex
ETDKYPstP3 u0XQD MbOna,wvl,eRZKd E,mzGgwvd,MYosTP5bNpxfx9rtmKg yBM251tTN6trt

Popular posts from this blog

PHP contact form sending but not receiving emails

Do graphics cards have individual ID by which single devices can be distinguished?

Create weekly swift ios local notifications