Search code examples
asp.netgeneric-handler

Asychronous Task works with HttpClient object but not with project's class in Web Generic Handler


I found an interesting differences between .NET Framework's HttpClient class/objects and the VS-2013 Project PhotoServer (DLL) class/objects. It made me wonder if there's a bug with the script.

I'm using .NET Framework v4.5.1.

I'm using the HttpClient script in the sychronous Web Generic Handler. Noticed that I'm using the ".Result" for the asynchronous POST to wait for response. So, looking at HttpClient which works is

 using (var httpClient = new HttpClient())
 {
     var response = httpClient.PostAsync(
         _baseUrl,
         new FormUrlEncodedContent
         (
             new List<KeyValuePair<string, string>>
             {
                 new KeyValuePair<string, string>("Vin", parmVin), 
                 new KeyValuePair<string, string>("ImageSize", parmImageSize)
             }.ToArray()
         )
     ).Result;

     //returned string[] datatype...
     var photoUrls = response.Content.ReadAsStringAsync().Result;
 }

I'm using the "GetPhotoUrlsAsync" script in the sychronous Web Generic Handler. This "GetPhotoUrlsAsync" object comes from the Project class (DLL). Again, I'm using the ".Result" and it doesn't work, it just deadlocked and hung. What I wanna know is why is that and was there a bug with the script?

 //[Scripts in Web Generic Handlers]...
 var managerVehiclePhoto = new ManagerVehiclePhoto();
 var photoUrls = managerVehiclePhoto.GetPhotoUrlsAsync("12345678901234567").Result;

 //[Project Class]...
 namespace BIO.Dealer.Integration.PhotoServer
 {
      public seal class VehiclePhotoManager
      {
          public async Task<string[]> GetPhotoUrlsAsync(string vin)
          {
              var listResponse = await _client.ListAsync(vin);
              return listResponse.ToArray();
          }
      }
 }

Thanks...

Edit #1

    //Synchronous API Call...

    public string[] GetPhotoUrls(string vin)
    {
        return GetPhotoUrlsAsync(vin).Result;
    }

Solution

  • Using .Result like this is actually a bug in both cases; it just happens not to deadlock in the HttpClient case. Note that the same HttpClient library on other platforms (notably Windows Phone, IIRC) will deadlock if used like this.

    I describe the deadlock in detail on my blog, but the gist of it is this:

    There's an ASP.NET "request context" that is captured by default every time you use await. When the async method resumes, it will resume within that context. However, types such as HttpContext are not multithread-safe, so ASP.NET restricts that context to one thread at a time. So if you block a thread by calling .Result, it's blocking a thread inside that context.

    The reason GetPhotoUrlsAsync deadlocks is because it's an async method that is attempting to resume inside that context, but there is already a thread blocked in that context. The reason HttpClient happens to work is because GetAsync etc. are not actually async methods (note that this is an implementation detail and you should not depend on this behavior).

    The best way to fix this is to replace .Result with await:

    var managerVehiclePhoto = new ManagerVehiclePhoto();
    var photoUrls = await managerVehiclePhoto.GetPhotoUrlsAsync("12345678901234567");