async await performance?

老子叫甜甜 提交于 2019-12-17 07:30:47

问题


(Just a theoretical question - for non-gui apps)

Assuming I have this code with many awaits:

public async Task<T> ConsumeAsync()
    {
          await A();
          await b();
          await c();
          await d();
          //..
    }

Where each task can take a very short period of time ,

Question (again , theoretical)

There could be a situation where the overall time dealing with all those "releasing back threads" and "fetching threads back" ( red & green here :)

Is taking more time than a single thread which could done all the work with a small amount of delay ,

I mean , I wanted to be the most productive , but instead , since all those switches back and forth - I actually lost productivity.

Can such scenario occur ?


回答1:


A Task object represent the deferred result of a pending operation. You don't have to use tasks and async/await if you don't have any pending operations. Otherwise, I believe async/await code is generally more efficient than its bare TPL ContinueWith analogue.

Let's do some timing:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication
{
    class Program
    {
        // async/await version
        static async Task<int> Test1Async(Task<int> task)
        {
            return await task;
        }

        // TPL version
        static Task<int> Test2Async(Task<int> task)
        {
            return task.ContinueWith(
                t => t.Result,
                CancellationToken.None,
                TaskContinuationOptions.ExecuteSynchronously,
                TaskScheduler.Default);
        }

        static void Tester(string name, Func<Task<int>, Task<int>> func)
        {
            var sw = new System.Diagnostics.Stopwatch();
            sw.Start();
            for (int i = 0; i < 10000000; i++)
            {
                func(Task.FromResult(0)).Wait();
            }
            sw.Stop();
            Console.WriteLine("{0}: {1}ms", name, sw.ElapsedMilliseconds);
        }

        static void Main(string[] args)
        {
            Tester("Test1Async", Test1Async);
            Tester("Test2Async", Test2Async);
        }
    }
}

The output:

Test1Async: 1582ms
Test2Async: 4975ms

So, by default, await continuations are handled more efficiently than ContinueWith continuations. Let's optimize this code slightly:

// async/await version
static async Task<int> Test1Async(Task<int> task)
{
    if (task.IsCompleted)
        return task.Result;
    return await task;
}

// TPL version
static Task<int> Test2Async(Task<int> task)
{
    if (task.IsCompleted)
        return Task.FromResult(task.Result);

    return task.ContinueWith(
        t => t.Result,
        CancellationToken.None,
        TaskContinuationOptions.ExecuteSynchronously,
        TaskScheduler.Default);
}

The output:

Test1Async: 1557ms
Test2Async: 429ms

Now the non-async version wins. In case with the async version, I believe this optimization has already been done internally by the async/await infrastructure.

Anyway, so far we've dealt only with completed tasks (Task.FromResult). Let's introduce the actual asynchrony (naturally, we'll do less iterations this time):

static Task<int> DoAsync()
{
    var tcs = new TaskCompletionSource<int>();
    ThreadPool.QueueUserWorkItem(_ => tcs.SetResult(0));
    return tcs.Task;
}

static void Tester(string name, Func<Task<int>, Task<int>> func)
{
    ThreadPool.SetMinThreads(200, 200);
    var sw = new System.Diagnostics.Stopwatch();
    sw.Start();
    for (int i = 0; i < 1000000; i++)
    {
        func(DoAsync()).Wait();
    }
    sw.Stop();
    Console.WriteLine("{0}: {1}ms", name, sw.ElapsedMilliseconds);
}

The output:

Test1Async: 4207ms
Test2Async: 4734ms

Now the difference is very marginal, although the async version still performs slightly better. Yet I think such gain is really neglectable, comparable to the actual cost of the asynchronous operation or to the cost of restoring the captured context for when SynchronizationContext.Current != null.

The bottom line is, if you deal with asynchronous tasks, go for async/await if you have a choice, not for performance reason but for ease of use, readability and maintainability.




回答2:


Yes, in theory. Not normally, in the real world.

In the common case, async is used for I/O-bound operations, and the overhead of thread management is undetectable in comparison to them. Most of the time, asynchronous operations either take a very long time (compared to thread management) or are already completed (e.g., a cache). Note that async has a "fast path" that kicks in if the operation is already completed, where it does not yield the thread.

For more information, see the Zen of Async and Async Performance.




回答3:


Yes, it can happen. Also do not forget that - all efficiency in there that you can program - the task system DOES have overhead.

If you get too granulkar with something like this, the synchronization overhead CAN kill you. THAT SAID: Tasks are quite efficienttly programmed.

But the old rule sticks: do not go super granular. SOmetimes optimization helps.




回答4:


Can such scenario occur?

Absolutely. For this reason, you should be conscientious about where you use async code. Typically you're best using it for methods that will actually perform an asynchronous operation (disk or network I/O, for example). The time that these operations take usually far outweighs the cost of scheduling tasks on threads. Also, at the operating system level, these sorts of operations are inherently asynchronous, so you're actually removing a layer of abstraction by using async methods.

Even in these cases, though, you will likely not see a noticeable performance difference by switching to asynchronous code unless you are able to take advantage of concurrency. For example, the code you posted would probably see no real performance gain unless it were changed to something like this:

await Task.WhenAll(new[]{A(), B(), C(), D(), ...});



回答5:


Yes, of course it can happen. With all the overhead of the creating the state machine, yielding back and forth the control and using IOCP threads. But as said, the TPL is pretty optimized. For example, lets not forget that if your TaskAwaitable finishes quickly, there might be no overhead and it will execute synchronously, which might happen often with quick operations.



来源:https://stackoverflow.com/questions/23871806/async-await-performance

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!