0

I have the following piece of code in a domain service:

HttpClient client = new HttpClient();
client.GetAsync("http://somewebsite.com").Result;

Which works fine, I can place a break point on followed lines and they do hit and all is good. However, I have the same line of code in a nuget package installed in this very same project. There there's the exact same http call:

public class Client : Base
{
    public Task<List<Stuff>> GetAsync()
    {
        return SendMessageAsync(HttpMethod.Get, "http://getstuff.com")
            .ContinueWith(x => JsonConvert.DeserializeObject<List<StuffView>>(x.Result.Content.ReasAsStringAsync().Result);
    }
}
public class Base
{
    HttpClient client:

    public Base(HttpClient client)
    {
        this.client = client:
    }
    protected async Task<HttpResponseMessage> GetMessageAsync(BttpMethod method, string url)
    {
        var request = CreateRequestAsync(method, url);
        request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        var response = await client.SendAsync(request); //line 1
        return response; // Line 2
    }
    protected HttpRequestMessage CreateRequestAsync(HttpMethod method, string url) 
    {
        var request = new HttpRequestMessage(method, url);
        request.SetBearerToken(myAccessTokenProvider);
        return request;
    }
}

This is how my domain service is using the nuget package code:

var client = factory.Create();
client.GetAsync().Result;

Debugging this code shows that inside Base class line 1 is hit but line 2 never does. Searching on internet it seems like a thread deadlock issue. So my question is, assuming it's a deadlock, why the heck the first http call in domain service works but not the second one that uses the nuget package code?!

HttpClient deadlock issue: HttpClient.GetAsync(...) never returns when using await/async

Community
  • 1
  • 1
  • 6
    Never use `.*Async().Result`. – SLaks Apr 20 '17 at 20:16
  • 1
    @SLaks what to use then? GetAwaiter().GetResults() has the same issue, never comes back. No errors to be found anywhere. –  Apr 20 '17 at 20:20
  • 2
    @Hani, you must `await` async calls to avoid deadlocks and to allow Asp.Net to operate as it was designed. – Crowcoder Apr 20 '17 at 20:21
  • That's great, why does the first example works then? –  Apr 20 '17 at 20:25
  • 3
    @Hani `x.Result.Content.ReasAsStringAsync().Result` is the cause of the problem. The first `.Result` is allowed as its task has already completed from the previous call. the second `.Result` is mixing async and blocking call. hence the deadlock. Try to avoid mixing async and blocking calls. it is either one or the other all the way through. – Nkosi Apr 20 '17 at 20:31
  • @Nkosi just removed the serialization and return type changed to HttResponseMessage and still the same issue. The way I see it, it seems that we don't know exactly in fine details how asp.net behaves in the first example? –  Apr 20 '17 at 20:38
  • The first example does not lock because the synchronization context is not waiting on two things. But it is still a mean thing to do to Asp.Net's thread model. Did you read the links Stephen Cleary listed in his answer to the question you linked to? – Crowcoder Apr 20 '17 at 20:44
  • @Crowcoder as I mentioned removing that piece of code didn't change anything, so when you say first example works because it's not waiting for two things, then second example should start working after modifying it to wait for one thing. But it does not. –  Apr 20 '17 at 20:54

2 Answers2

1

Do not use .Result. With .Result, the I/O is started (asynchronously) where the calling thread (synchronously) blocks waiting for it to complete.

Just use await.

var stuff = await client.GetAsync();
William Xifaras
  • 5,212
  • 2
  • 19
  • 21
1

The first example works because either the Task is in the .Completed == true state before you call .Result on it or that line of code has SyncronisationContext.Current == null.

If the task is not in the completed state calling .Result or .Wait() on a task can result in a deadlock if SyncronisationContext.Current != null at the point you called .Result or .Wait(). The best thing to do is mark the entire call stack as async and start returning Task instead of void and Task<T> where you return T then calling await on all places you used .Result or .Wait().

If you are not willing to make those code changes you must switch to using non async methods, this will requite switching from HttpClient to WebClient and using it's non async method calls to do your requests.

Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • Thanks Scott, any idea why the first task would complete so fast but the second one doesn't while they call the same web resource? here I'm assuming this is a timing issue that causes one task to be completed since there is no manual intervention to do that or mark SynchronizationContext as null. –  Apr 20 '17 at 21:04
  • If the work is being done on a background thread also sets it null. Doing work in a `.ContinueWith` can also cause it. – Scott Chamberlain Apr 20 '17 at 21:05
  • What exactly sets the SynchronizationContext and how can I change that behavior, if at all? –  Apr 20 '17 at 21:43
  • 1
    The Framework you are using ex: Winforms, WPF, or ASP.NET sets it on the "UI Thread". See [this article](https://msdn.microsoft.com/en-us/magazine/gg598924.aspx) for more info on how it works – Scott Chamberlain Apr 20 '17 at 22:06