Async-await Task.Run vs HttpClient.GetAsync

22,083

Solution 1

I'd rather use the second implementation as it will require less 'async' signatures on methods in my code.

That sounds like a very odd justification. You're trying to execute fundamentally "somewhat asynchronously" - so why not make that clear?

Is there any difference between the two implementations?

Absolutely. The second implementation will tie up a thread while WebClient.DownloadString blocks, waiting for the request to complete. The first version doesn't have any blocked threads - it relies on a continuation to fire when the request finishes.

Additionally, consider your Logger.Error call. In the async version, that will still execute in the context of the original calling code. So if this is in, say, a Windows Forms UI, you'll still be on the UI thread, and you can access UI elements etc. In the second version, you'll be executing in a thread pool thread, and would need to marshal back to the UI thread to update the UI.

Note that your async void method almost certainly shouldn't be async void. You should only make an async method return void for the sake of complying with event handler signatures. In all other cases, return Task - that way the caller can see when your task has finished, handle exceptions etc.

Also note that you don't need to use HttpClient for asynchrony - you could use WebClient.DownloadStringTaskAsync instead, so your final method could become:

private async Task<string> DownloadHtmlAsync(string query)
{
    using (var client = new WebClient())
    {
        try
        {
            return await client.DownloadStringTaskAsync(query);
        }
        catch (Exception ex)
        {
            Logger.Error(msg, ex);
            return null;
        }
    }
}

Solution 2

For server applications, async is about minimizing the number of blocked threads you've got: increasing the efficiency of the thread pool and perhaps allowing your program to scale to more users.

For client applications where you're unlikely to need to care about thread count, async provides a relatively easy way to keep your UI running fluid when you perform I/O.

It is much different from Task.Run underneath the hood.

Solution 3

You will only benefit from async processing if your calling thread has something meaningful to do, like keeping the UI responsive. If your calling thread only starts a task and does nothing but wait until the task is finished, your process will run slower.

Your second implementations starts a task, but you await for it to finish without doing anything else. This way you won't benefit.

You don't describe your environment, but if you have a UI that must keep responsive, then the the implementation of method 1 is ok, except that your Start() is not declared async and does not await:

private async Task StartAsync()
{
    foreach (var url in urls)
    {
        await ParseHtml(url)
    }
}

You can call this from an event handler as follows:

private async void OnButton1_clicked(object sender, ...)
{
   await StartAsync();
}

Note: the ParseHtml has is preceded by await. The next html will be parsed after the previous parse has finished. However because the parse is async, the calling thread (the UI thread?) will be able to do other things like respond to user input.

However, if your parseHTML function is able to run simultaneously the following code would be preferable, and probably faster:

private async Task StartAsync()
{
    var tasks = new List<Task>()
    foreach (var url in urls)
    {
        tasks.Add(ParseHtml(url));
    }
    // now you have a sequence of Tasks scheduled to be run, possibly simultaneously.
    // you can do some other processing here
    // once you need to be certain that all tasks are finished await Task.WhenAll(...)
    await Task.WhenAll(tasks);
    // Task.WhenAls(...) returns a Task, hence you can await for it
    // the return of the await is a void
}
  • If you call a function that returns a Task, you can continue doing other things while the task is running or await for the task to finish and use the results of the task.
  • if you await, your code stops, but your callers continue processing until they await for your task to complete
  • you can only await in your procedure if your procedure is async.
  • async functions can only be called by other async functions, or by calling Task.Run( () => ...) or if preferred: Task.Factory.StartNew( () => ...)
  • Instead of void an async function returns Task
  • Instead of TResult an async function return Task<TResult>
  • The only exception is the event handler: declare it async and return void.
  • If you need the task to finish, just await for the task.
  • The return of the await is the TResult.
Share:
22,083
vondip
Author by

vondip

I am a an entrepreneur and developer. Working on my startup - A simple photoshop for non professionals. You can check it out on: http://bazaart.me

Updated on April 17, 2021

Comments

  • vondip
    vondip about 3 years

    I'm new to c# 5's async feature. I'm trying to understand the difference between these two implementations:

    Implementation 1:

    private void Start()
    {
        foreach(var url in urls)
        {
            ParseHtml(url);
        }
    }
    
    private async void ParseHtml(string url)
    {
        var query = BuildQuery(url); //BuildQuery is some helper method
        var html = await DownloadHtml(query);
        //...
        MyType parsedItem = ParseHtml(html);
        SaveTypeToDB(parsedItem);
    }
    
    private async Task<string> DownloadHtml(string query)
    {
        using (var client = new HttpClient())
        try
        {
            var response = await client.GetAsync(query);
            return (await response.Content.ReadAsAsync<string>());
        }
        catch (Exception ex)
        {
            Logger.Error(msg, ex);
            return null;
        }
    }
    

    Implementation 2:

    private void DoLoop()
    {
        foreach(var url in urls)
        {
            Start(url);
        }
    }
    
    private async void Start(url)
    {
        await Task.Run( () => ParseHtml(url)) ;
    }
    
    private void ParseHtml(string url)
    {
        var query = BuildQuery(url); //BuildQuery is some helper method
        var html = DownloadHtml(query);
        //...
        MyType parsedItem = ParseHtml(html);
        SaveTypeToDB(parsedItem);
    }
    
    private string DownloadHtml(string query)
    {
        using (var client = new WebClient())
        {
            try
            {
                return client.DownloadString(query);
            }
            catch (Exception ex)
            {
                Logger.Error(msg, ex);
                return null;
            }
        }
    }
    

    I'd rather use the second implementation as it will require less 'async' signatures on methods in my code. I'm trying to understand what's the benefit of using the HttpClient class vs using a new Task and awaiting it instead?

    Is there any difference between the two implementations?

  • vondip
    vondip over 11 years
    Thanks! Quick question though. Let's take ParseHtml in the first implementation. How would I return the Task object propagated from DownloadHtml? When I use its value it gets converted to "MyType"
  • Jon Skeet
    Jon Skeet over 11 years
    @vondip: I don't understand what you mean, I'm afraid. Possibly ask that in more detail as a separate question?