0

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;
    }
fletchsod
  • 3,560
  • 7
  • 39
  • 65

2 Answers2

2

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");
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • I added Edit #1 code above. That function when called in non-async webpage (not part of Web Generic Handler), it works fine in .NET framework 4.5.0 but not in 4.5.1. So, do you know what broke it or what had changed in the .NET framework? Thanks. – fletchsod Jul 16 '14 at 18:52
  • @fletchsod: No; AFAIK the ASP.NET "request context" did change a bit from 4.0 to 4.5, but not from 4.5.0 to 4.5.1. – Stephen Cleary Jul 16 '14 at 19:08
1

Me again (re: same quest WebPages async) :) That said, I'm one of those that Stephen Cleary identifies as "trying to migrate into async", so all this is (still) a learning moment.

The issue is SynchronizationContext in GUI/ASP.Net. I won't mangle Stephen's explanation so the link is your best bet to grok.

Given the best practices in that article, here's my way (consequently what I use in WebPages for the "top level call") to "mock" awaiting a PostAsync call like what you're doing. In this case I'm using ConfigureAwait (call is from WebPages, not MVC):

public static async Task<string> PostToRequestBin()
{       
    var _strContent = new FormUrlEncodedContent(new[] {new KeyValuePair<string,string>("fizz","buzz")});

    using (var client = new HttpClient())
    {

        /*
         * See http://requestb.in/ for usage             
         */
        var result = await client.PostAsync("http://requestb.in/xyzblah", _strContent).ConfigureAwait(false);

        return await result.Content.ReadAsStringAsync();

    }
}

In my WebPages page:

@{

   //Class2 is a mock "library" in App_Code where the above async code lives
   var postcatcher = Class2.PostToRequestBin(); 
}

And to make use of it somewhere in the page (where I make use of Task<string>.Result:

<p>@postcatcher.Result</p>

Again, this is a learning moment and I hope it helps/guides you. I fully expect the SO community to comment and or correct/improve on this like:

"Why don't I have to ConfigureAwait on ReadAsStringAsync" (it works either way)?

  • because at this point, it's "async all the way". I could have awaited some other async method...

...so the learning moments continue :)

EdSF
  • 11,753
  • 6
  • 42
  • 83