简体   繁体   中英

Async Await performance - Direct method call vs Task wrapper call

A small program that I have created to understand the working of Async Await and calling the async method in a for loop, as a direct method call:

sumProgram.CallSum(i, i + 1);

or using the Task APIs Task.Run / Task.Factory.StartNew

My initial understanding was Task API would make it substantially faster, but contrary to my expectation, which certainly reflect lack in my understanding, direct call is much better in terms of performance. In fact when an additional Thread sleep is introduced in the GetSum method, it seems to be impacting only Task calls and that too by a big margin.

Now I understand the first part of direct call being faster, since they are made to execute asynchronously and there's no overhead of adding to a list of tasks and making them wait, but when I would shift the same technique to real programming paradigm, then question remains:

  • For direct call, there's nothing to emulate Task.WaitAll, so they would exit the calling method, even if all the executions are not complete, so is my only option Task wrapper.

  • Am I getting convoluted results, since for the direct call the stopwatch will publish time even before all executions are complete and that would not be the case for the Task wrapper due to waitAll

  • For executing this program, you need comment / uncomment the relevant portions for correct results

      class Program { static void Main(string[] args) { Program sumProgram = new Program(); List<Task> taskList = new List<Task>(); // Only For Task Stopwatch sw = Stopwatch.StartNew(); for (int i = 0; i < 100000; i++) { taskList.Add(Task.Factory.StartNew(() => { sumProgram.CallSum(i, i + 1); })); // For Task use one taskList.Add(Task.Run(() => { sumProgram.CallSum(i, i + 1); })); // For Task use one sumProgram.CallSum(i, i + 1); } Task.WaitAll(taskList.ToArray()); // Only For Task sw.Stop(); Console.WriteLine("Time Elapsed :: " + sw.ElapsedMilliseconds); } public async Task CallSum(int num1, int num2) { Func<int> callFunc = (() => { return GetSum(num1, num2); }); int result = await Task.Run<int>(callFunc); //Console.WriteLine("Sum Result :: " + result); } public int GetSum(int num1, int num2) { Thread.Sleep(10); return (num1 + num2); } } 

async != "faster"

In fact, async code is almost always (slightly) slower.

So, why use async ?

On client-side (UI) applications, async allows you to remain responsive to the user. Contrast this code:

void Button_Click()
{
  Thread.Sleep(10000);
}

with this code:

async void Button_Click()
{
  await Task.Delay(10000);
}

On server side (eg, ASP.NET), async allows your code to use fewer threads to serve more requests. Thus, increased scalability.

Note that in both of these scenarios, the async code is actually slower than its synchronous counterpart. For the UI, it is faster to sleep the current thread than it is to create and start a timer, create and await a task, and then resume the async method when the timer fires. For ASP.NET, it is faster to block the current thread than it is to create a task for the asynchronous operation, and then switch the request context to another thread when the async method resumes.

However, async brings other benefits. For the UI, the UI thread is not blocked during the delay. For ASP.NET, the request thread is free to work on other requests while the asynchronous operation is in progress.

async is not about speed; it is about freeing up the current thread.

The comments above are saying that you have too little work being done inside the Task for it to outweigh it's setup costs.

Also, Tasks and async-await aren't meant for scenarios where direct calls are just as easily possible. Making something asynchronous doesn't make it faster. They're meant for scenarios where you have keep your calling thread running/doing something else while waiting for a lengthy/failable/cancellable Task to complete or distribute enough work across cores in a way that the hardware will actually parallelize. And doing all this without ugly, inside-out code that characterizes asynchronous programming.

Your experiment, quite simply, is wrong. What you've measured is the overheard of using the Task mechanism in .NET in a scenario where it couldn't possibly win.

Try this instead. In each task, copy some memory from one location to another (allocated using Marshal.AllocHGlobal and copied using P/Invoke CopyMemory). Once you hit a critical threshold, you'll see how much faster this mechanism is compared to single-threaded copies. Also, using async-await your code will look a lot nicer.

The big problem with your code is that node of the options you're using are waiting for CallSum() to complete. To do that, take the Task that's returned by CallSum() and wait for it. For example:

taskList.Add(sumProgram.CallSum(i, i + 1));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM