1

I am using HttpClient to make a request to an api. This code is located in a class library project shared with two aditional projects, a Console and a Asp.Net Mvc project. When I make a request from the Console project it works great, but in the asp project it blocks in the line

using(Stream responseStream = await response.Content.ReadAsStreamAsync()

this is my request code

private async Task<dynamic> ReadJson(string url)
        {
            HttpResponseMessage response = await httpClient.GetAsync(url);

            if (response.StatusCode == System.Net.HttpStatusCode.NoContent)
                throw new RateLimitException();

            if (response.StatusCode == System.Net.HttpStatusCode.Forbidden)
                throw new AccessDeniedException();

            if (response.StatusCode != System.Net.HttpStatusCode.OK)
                throw new Exception("Error: " + response.StatusCode);

            using (Stream responseStream = await response.Content.ReadAsStreamAsync())
            using (StreamReader sr = new StreamReader(responseStream, System.Text.Encoding.UTF8))
            {
                string json = sr.ReadToEnd();
                return JObject.Parse(json);
            }
        }

I am making the same call to the method from the Console and the Asp.Net project. From the console works but the asp .net project blocks in the line when reads the response content

7
  • Try calling the async methods synchronously like this: httpClient.Get(url); and response.Content.ReadAsStream() and tell me if still blocks. Commented Jan 27, 2017 at 17:33
  • 1
    You need to make the action in MVC controller async as well Commented Jan 27, 2017 at 17:35
  • @Alisson httpClient dont have a Get() method and Content dont have ReadAsStream(). Im I missing some namespace? Commented Jan 27, 2017 at 17:39
  • 1
    Show the controller action that calls ReadJson function Commented Jan 27, 2017 at 17:40
  • @AlexArt. My controller is not async but I am doing this: var task = ReadJson(...); task.Wait(); and using the Result property of task. Why should I use an async controller? Commented Jan 27, 2017 at 17:41

1 Answer 1

6

Most probably this deadlock occurs because the controller action that calls ReadJson function is synchronous. You need to make the action async. You can find an excellent explanation of this deadlock here. (All the credits go to Stephen Cleary)

Quick summary is:

/ My "library" method.
public static async Task<JObject> GetJsonAsync(Uri uri)
{
  using (var client = new HttpClient())
  {
    var jsonString = await client.GetStringAsync(uri);
    return JObject.Parse(jsonString);
  }
}

// My "top-level" method.
public class MyController : ApiController
{
  public string Get()
  {
    var jsonTask = GetJsonAsync(...);
    return jsonTask.Result.ToString();
  }
}

What Causes the Deadlock

  1. The top-level method calls GetJsonAsync (within the UI/ASP.NET context). GetJsonAsync starts the REST request by calling HttpClient.GetStringAsync (still within the context).

  2. GetStringAsync returns an uncompleted Task, indicating the REST request is not complete.

  3. GetJsonAsync awaits the Task returned by GetStringAsync. The context is captured and will be used to continue running the GetJsonAsync method later. GetJsonAsync returns an uncompleted Task, indicating that the GetJsonAsync method is not complete.

  4. The top-level method synchronously blocks on the Task returned by GetJsonAsync. This blocks the context thread.

  5. … Eventually, the REST request will complete. This completes the Task that was returned by GetStringAsync.

  6. The continuation for GetJsonAsync is now ready to run, and it waits for the context to be available so it can execute in the context.

  7. Deadlock. The top-level method is blocking the context thread, waiting for GetJsonAsync to complete, and GetJsonAsync is waiting for the context to be free so it can complete.

Preventing the Deadlock

There are two best practices that avoid this situation:

  1. In your “library” async methods, use ConfigureAwait(false) wherever possible.
  2. Don’t block on Tasks; use async all the way down.
Sign up to request clarification or add additional context in comments.

2 Comments

My controller is not async but i am calling the wait method from the task returned by the ReadJson method. Like @Alisson said, it is a good practice, but it does not should be the cause to my problem. I will tray anyway.
"My controller is not async but i am calling the wait method.." That is precisely the problem. Don't do that - it causes deadlocks. Make your controller async.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.