Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Async Await Performance in Web Applications

So far I think I've grasped the concepts of how async await can make your application more responsive but I'm hung up on two points:

Layer Considerations Does async await have to go from the repository layer, all the way up to the MVC or WCF tier to get the performance benefits or can I just do async operations on my repository methods that are taking a long time?

"await" usage If I can just work at the repository level there's one part I don't understand. Using this (low-tier) approach, will the thread be able to service incoming client requests while waiting on the io bound code to finish?

I put together a sample console application, in my mind, while the long running task is continuing, another user could make a request to my web application. Using my little library (to ease integration and exception handling), would their request be serviced by the pending thread or does the voidTask.Wait(); cause the thread to block?

public class AsyncTest
{
    public void RunTest()
    {
        try
        {
            using (var task = new RunTask(LongRunningTask))
            {
                Console.WriteLine("Echo two things:");

                for (int i = 0; i < 2; i++)
                {
                    var input = Console.ReadLine();
                    Console.WriteLine(string.Format("Echoing \"{0}\"", input));
                }
            }
        }
        catch (Exception e)
        {
            Console.WriteLine("Error, hm, what happened??");
            Console.WriteLine();
            Console.WriteLine(e.Message);
            Console.WriteLine(e.StackTrace);
        }
    }

    public void LongRunningTask()
    {
        var totals = 0;

        for (int i = 0; i < 30; i++)
        {
            Thread.Sleep(1000);
            totals += 5;

            if (i == 25)
                throw new ArgumentException("I can't handle this! Errorr!!@!@");

            Console.WriteLine(string.Format("LongRunningTask Step {0}...", (i + 1)));
        }
    }
}

public class RunTask : IDisposable
{
    public delegate void IOBoundOperations();
    private Task voidTask;

    public RunTask(IOBoundOperations task)
    {
        voidTask = Execute(task);
    }

    async public Task Execute(IOBoundOperations task)
    {
        await Task.Run(() =>
        {
            task();
        });
    }

    public void Dispose()
    {
        try
        {
            voidTask.Wait();
        }
        catch
        {
            throw new AsyncException("Failed to run task asynchronously: " + 
                voidTask.Exception.InnerException.Message, voidTask.Exception.InnerException);
        }
    }
}

I put in the wait, because I'll need it for tasks that will return data to the calling code that it may depend on. I also did it because I noticed the process won't complete until the thread is done executing my asynchronous stuff as well.

I can find lots of information on async/await and it's benefits, just nothing on this on this one particular aspect.

like image 550
Matthew Rhoden Avatar asked Jan 21 '26 16:01

Matthew Rhoden


2 Answers

As others said, you're not actually grasping the real power of async IO with your example.

People are often "scared" when they realize that async goes "all the way", from the repository all the way up to your controller. As I always say, don't fight it, let it naturally grow in your codebase as you start implementing it. I recommend not doing any shortcuts with sync over async and vice versa, put in the extra effort (if needed) of exposing both API's separately.

But, as @usr said, it isn't always needed to actually do async IO, even of it's possible. Of course, everybody wants to ride the async-await bandwagon because it's the cool thing to do, but ask yourself before, am I going to be hitting alot of concurrent requests where I will actually benefit from async IO? Don't forget that although minimal, async-await does have some overhead to it.

To conclude, a little personal story. Recently I started working on a ASP.NET Web API project which was hitting a SQL database and doing some heavy computations. The challange was to make the computation go faster, and I wondered if making the database calls async would help (I had a gut feeling it wouldn't, but I had to try). After adding the asynchronous API and bubbling it throughout the methods (which by itself wasn't a small effort), the overall performance actually ended up degrading (as I assumed). So, make sure you take advantage of async IO for the right reasons.

like image 101
Yuval Itzchakov Avatar answered Jan 23 '26 06:01

Yuval Itzchakov


To get any benefits from the async/await paradigm, you will want to bring it all the way to the top, be that your MVC/Web API action (via marking the action as async and returning a Task of some sort) or in your Web Forms methods (via marking them as async void, typically, and setting the Async attribute on the page itself).

Aside from that, you'd only have two options:

  • Call Task.Wait(), which does make it block, so you won't get any asynchronous behavior.
  • Just let it float away by not joining to that thread in any way, which probably isn't ideal (even for things like logging that don't return anything, the IIS Thread Pool system doesn't like floating threads, so avoid the use of that here).

That all said, you mention that another user could call into your web server if you did this, which is true, but it's important to note that ASP web applications are always "asynchronous" in that they have many threads available to them. Implementing async/await is a good idea on principle, and it helps improve efficiency of those allocated threads, and potentially bumps up the number of available concurrent connections, but it is not required to allow, say, two concurrent requests to be fulfilled at once. The Thread Pool will handle that on its own.


If you're worried about going through your entire codebase and swapping out synchronous calls with their ...Async() counterparts, remember that you don't necessarily have to do everything at once. It's good to if you can, just because asynchronous calls have some advantages listed before, but remember that you can just set your return method to return Task.FromResult(object) to uphold an asynchronous contract.

Obviously this doesn't behave asynchronously, but it's nice to keep in mind when you're migrating existing code or writing a library, because time will only make more and more stuff implement that pattern, and you can design to welcome it now, rather than wishing you did later.

like image 42
Matthew Haugen Avatar answered Jan 23 '26 05:01

Matthew Haugen