MattSull
MattSull

Reputation: 5564

C# asynchronous method - help needed

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.

Upvotes: 0

Views: 2348

Answers (3)

Darrel Miller
Darrel Miller

Reputation: 142014

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");

Upvotes: 0

gdp
gdp

Reputation: 8232

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.

Upvotes: 1

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.

Upvotes: 5

Related Questions