0

I have an asynchronous method that consumes a Web API action. It appears to be stuck in a loop. My reasoning for this is because if I put a break point on line 1 of the catch block, and step into, it never actually hits the second line.

I was initially returning a data set with 100,000+ (~30mb) rows and thought that it could just be slow due to the size of the request, but after changing my Web API action to return only 1 row the problem still persisted. The data is definitely being returned as when I browse to the URI of the Web API solution I'm getting JSON returned in my browser.

public async Task<IEnumerable<Document>> GetAll()
{
    try
    {
        var response = await _client.GetAsync(
                       string.Format("{0}/api/document", _baseURI));

        // never hits line below
        return await response.Content.ReadAsAsync<Document>() 
                   as IEnumerable<Document>;
    }
    catch (Exception ex)
    {
        // handle exception
    }
}

I'm not sure if I'm missing something here? Some help would be appreciated.

EDIT 1

In response to some questions, I have a Web API project that's referenced by a MVC project. I've had to make some changes from original question for JSON deserialization.

Repository:

public async Task<IEnumerable<Document>> GetAll()
{
    try
    {
        string json;
        var response = await _client.GetAsync(string.Format(
                       "{0}/api/document", _baseURI)).ConfigureAwait(false);

        var resource = await response.Content.ReadAsAsync<Document>();

        using(var reader = new StreamReader(resource.ToString()))
        {
            json = reader.ReadToEndAsync().ToString();
        }

        return JsonConvert.DeserializeObjectAsync<Document>(json) 
                       as IEnumerable<Document>;
        }
        catch (Exception)
        {
            throw;
        }
    }

Controller:

public async Task<ActionResult>  GetAll()
{
    return PartialView("_GetAllDocumentsPartial", await _docRepo.GetAll());
}

With changes described in answers below, the same problem still occurs when debugging as above. However I get "A task was canceled." exception on the catch in the method in the repository.

Stack trace.

MattSull
  • 5,514
  • 5
  • 46
  • 68
  • 3
    **Never** write `throw ex`. http://stackoverflow.com/a/2999314/34397 – SLaks Jul 12 '13 at 15:49
  • 1
    Are you sure its never hitting the second line? there are issues with async and debugging... you might try putting a break point on the second line as well just to be sure. – Tim Jul 12 '13 at 15:50
  • Yes, just placed a break point there and it doesn't hit it. Chrome just continues to load/page is blank. – MattSull Jul 12 '13 at 15:52
  • To further solidify your assertion about the last sentence before the example, have you tried using fiddler to make sure that the the data is being returned to your actual application? That is, assuming you're running this on a machine where you can get fiddler in between the two... – Tim Jul 12 '13 at 15:55
  • There's no JSON being returned in Fiddler. The GET never actually completes. The service is definitely available though as browsing to the URI directly returns the JSON. – MattSull Jul 12 '13 at 16:05
  • Are you working on an external library outside your main app? – gdp Jul 12 '13 at 16:48
  • I think you should have the ConfigureAwait(false) on all your await calls – mao47 Jul 16 '13 at 19:03
  • @mao47 tried and didn't work. – MattSull Jul 17 '13 at 08:19

3 Answers3

5

Are you calling GetAll().Result from an ASP.NET application? Since you have tagged MVC to this question, I assume so. If you do, then you are deadlocking yourself.

Say you calling web API like this.

public ActionResult Index()
{
    var result = GetAll().Result;

    return View();
}

The problem here is when the async call completes, it needs to continue on the original thread where it jumped out but you are blocking that thread by calling Result(). Call to Result blocks and waits for async call to return and the async continuation waits for the ASP.NET thread to be available to continue. That is a dead lock.

Change your GetAll() like this.

public async Task<IEnumerable<Document>> GetAll()
{
    try
    {
        var response = await _client.GetAsync(
                       string.Format("{0}/api/document", _baseURI))
                           .ConfigureAwait(false);

        // never hits line below
        return await response.Content.ReadAsAsync<Document>() 
                   as IEnumerable<Document>;
    }
    catch (Exception ex)
    {
        // handle exception
    }
}

This indicates the context need not be preserved when the async call is complete and hence the continuation happens in a thread pool thread (not ASP.NET).

Better yet change the action method to public async Task<ActionResult> Index() and await GetAll().

If you find this answer helpful, you must only thank Stephen Cleary.

  • Apologies in delay in replying. I never actually did as you described in your answer ie `var result = GetAll().Result;`. Please see updated question. – MattSull Jul 16 '13 at 16:46
  • When I add `.ConfigureAwait(false)` and debug it steps onto the next line, but, with that in place respsonse doesn't contain a definition for `content` ie I can't go `respsonse.content...`. How can I access the `Result` of the response when I use `.ConfigureAwait(false)`? – MattSull Jul 17 '13 at 16:32
  • Something like `var x = await response.GetAwaiter().GetResult()...` works. Some other minor issues stopping me getting to where I want to be, maybe for another question though. – MattSull Jul 17 '13 at 18:27
1

Are you working in a external library? If so try adding ConfigureAwait(false);

var response = await _client.GetAsync(string.Format("{0}/api/document", _baseURI))
                            .ConfigureAwait(false);

Its to do with telling await not to capture the current context. Calling ConfigureAwait will ensure the rest of the method is executed using the ThreadPool. Some good reading on the subject can be found here on Stephen Cleary's blog.

gdp
  • 8,032
  • 10
  • 42
  • 63
  • Yes I have an MVC project that consumes a Web API project. I've made changes as per your answer. Please see updated question. – MattSull Jul 16 '13 at 18:57
0

If it works with a web browser but not using code then you probably need to send an accept header. Add the following line

_client.DefaultRequestHeaders.Accept = new MediaTypeWithQualityHeaderValue("application/json");
Darrel Miller
  • 139,164
  • 32
  • 194
  • 243