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

8
  • 6
    Never use .*Async().Result. Commented Apr 20, 2017 at 20:16
  • 1
    @SLaks what to use then? GetAwaiter().GetResults() has the same issue, never comes back. No errors to be found anywhere. Commented Apr 20, 2017 at 20:20
  • 2
    @Hani, you must await async calls to avoid deadlocks and to allow Asp.Net to operate as it was designed. Commented Apr 20, 2017 at 20:21
  • That's great, why does the first example works then? Commented Apr 20, 2017 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. Commented Apr 20, 2017 at 20:31

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

4 Comments

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.
If the work is being done on a background thread also sets it null. Doing work in a .ContinueWith can also cause it.
What exactly sets the SynchronizationContext and how can I change that behavior, if at all?
The Framework you are using ex: Winforms, WPF, or ASP.NET sets it on the "UI Thread". See this article for more info on how it works
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();

2 Comments

Sure, buy why is the first example work fine?! That's doing a .Result too.
@Hani Nkosi's comment above. Its spot on.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.