Vilo Vilo - 1 month ago 26
C# Question

ASP MVC4 await with httpclient

I'm learning how to use Tasks in MVC and I get completly lost. I need to download the source from selected webpage, find one element, get its value and return it. That's it.
For this I am using HtmlAgilityPack and HttpClient to fetch the webpage.
The problem that occurs is where nothing is waiting for response from httpClient and thus results that response generation completed when Task was still in progress. (An asynchronous module or handler completed while an asynchronous operation was still pending.)

I read lots of threads in here ,codeproj and some blogs, still don't understand what's the problem. Most common explanation is about resulting type of void in async method, but I cannot find any other way to return awaiting value, than this:

public float ReadPrice(Uri url)
{
switch (url.Host)
{
case "www.host1.xy":
return ParseXYZAsync(url).Result;
default:
return float.Parse("99999,99");
}
}

private Task<float> ParseXYZAsync(Uri url)
{
loadPage(url);

var priceNode = document.DocumentNode.SelectSingleNode(
@"//*[@id='pageWrapper']/div[4]/section[1]/div[4]/div[1]/div[1]/span");
var price = priceNode.InnerText;
...
return priceInFloat;
}

private async Task LoadPage(Uri url)
{
HttpClient http = new HttpClient();
var response = await http.GetByteArrayAsync(url);

String source = Encoding.GetEncoding("utf-8")
.GetString(response, 0, response.Length - 1);
source = WebUtility.HtmlDecode(source);

document.LoadHtml(source);
}

Answer

In order to figure out what's wrong you need to understand one key concept with async-await. When an async method hits the first await keyword, control is yielded back to the calling method. This means that when you do this:

loadPage(url);

The method will synchronously run until it hits:

var response = await http.GetByteArrayAsync(url);

Which will yields control back to ParseWebSite, which will continue execution and will probably end before the async operation has actually completed.

What you need to do is make LoadPage return a Task and await for it's completion:

private async Task<float> ParseWebsiteAsync(Uri url)
{
    await LoadPageAsync(url);

    var priceNode = document.DocumentNode.SelectSingleNode
        (@"//*[@id='pageWrapper']/div[4]/section[1]/div[4]/div[1]/div[1]/span");
    var price = priceNode.InnerText;
    return priceInFloat;
}

private async Task LoadPageAsync(Uri url)
{
    HttpClient http = new HttpClient();
    var source = await http.GetAsStringAsync(url);

    source = WebUtility.HtmlDecode(source);
    document.LoadHtml(source);
}

Side Notes:

  1. Do follow .NET naming conventions. Methods should be PascalCase and not camelCase
  2. Do add the "Async" postfix for asynchronous methods. E.g, LoadPage should become LoadPageAsync.
Comments