3

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.

Matthew Rhoden
  • 658
  • 1
  • 8
  • 20
  • The code you posted does not do any real async work. It just schedules a long running task that calls a sync delegate. Also note that in your example, the UI thread will block as soon as you have "echo"-ed twice, due to the `voidTask.Wait` in the `Dispose` method of your `RunTask` class. I don't see how that code is related to your question. – Alex May 08 '15 at 20:37
  • Yes, I was trying to get it to run async, but without the thread being forced to end when the application does. My thinking is, a request comes in, I kick off a long running task and continue. I don't want that long running task to end without completing just because the request was finished being serviced. That's why I called, `wait`. I watched one video with Richter explaining that it wasn't necessary to keep the Task around, but in a console application the thread stopped executing once it was finished. – Matthew Rhoden May 11 '15 at 16:09
  • The link I was referring to: http://channel9.msdn.com/Shows/AppFabric-tv/AppFabrictv-Threading-with-Jeff-Richter (slightly different situation I know, that's why I first confirmed in the code above. – Matthew Rhoden May 11 '15 at 16:10

4 Answers4

3

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.

Yuval Itzchakov
  • 136,303
  • 28
  • 230
  • 296
1

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.

Matthew Haugen
  • 11,855
  • 5
  • 34
  • 52
  • Thanks for your second point, we were thinking that we have some tasks that aren't necessary from the user's perspective but that still need done. I'm aware that IIS will manage the requests for me, I just wasn't sure if the `wait` would block or release that thread for reuse. I guess I could have worded that better. – Matthew Rhoden May 11 '15 at 16:19
0

You don't seem to have a firm grasp on why async IO is beneficial. It unblocks threads which saves stack memory and avoids overloading the thread-pools capacity.

Wait clearly blocks a thread because that function does not return until the target task has completed. During the call thread resources are consumed.

You need to make the entire call chain async. If you only make lower layers async then you have to wait at the upper layers and consume the same amount of threads that you save.

Note, that most web apps are not able to increase throughput or lower latency by using async IO. You have to understand what async IO is good for to make good calls. Don't fall for the hype. See Should we switch to use async I/O by default? and Why does the EF 6 tutorial use asychronous calls?.

Community
  • 1
  • 1
usr
  • 162,013
  • 33
  • 219
  • 345
  • Just to clarify, in our application we're servicing ~3,500 sessions for 30 minutes in our load tests (which are similar to a previous big release). Our trouble spots take up to a minute or more to complete because the underlying queries can be very complicated. These have already been converted to sproc's, so would running these async help? In the video I linked on my question above (in the comments) Richter hinted that the driver would release the thread while waiting. I guess that's only if the full call is async though? – Matthew Rhoden May 11 '15 at 16:14
  • 1
    1 min long calls are a good case for async. On the other hand the database probably can't process more than a few concurrently anyway (few = number of cores or disks on the server). So you can't have many concurrent requests anyway and async does not help one bit. – usr May 11 '15 at 19:35
0

Using async/await can be very handy with all I/O related actions. An example action is SaveAnalytics() what takes 100ms. if this is done synchronous the thread is blocked for that 100ms. if this is done asynchronous the thread is released immediately before all data of SaveAnalytics() is saved to file/database/whatever. when this saving is done another thread is used to return flow to client.

so what are the advantages of this? threads are expensive! if a highly scalable application is needed in lets say the cloud you can win a lot with making all your I/O related calls aynchronous.

if your web application is under heavy load you won't get the "503" message what means "Server to busy", what also means there are no threads available in the threadpool to hand out to client (they can all be busy with synchronous wait).

Its possible the user will notice nothing of this because they push the "Save" button and see a message that the "Analytics" are saved. Under water it all happens asynchronous.

So to recap, it all about the scalability of your application. If your application is not used heavily then its not worth to make all your methods asynchronous.