async all the way down issue

前端 未结 2 1594
面向向阳花
面向向阳花 2021-01-18 13:43

I have an async asp.net controller. This controller calls an async method. The method that actually performs the async IO work is deep down in my application. The series of

相关标签:
2条回答
  • 2021-01-18 14:20

    Yes. There is a penalty (though not a huge one), and if you don't need to be async don't be. This pattern is often called "return await" where you can almost always remove both the async and the await. Simply return the task you already have that represents the asynchronous operations:

    private Task C_Async(int id)
    {
        // This method executes very fast
        var idTemp = paddID(id);
        return D_Async(idTemp);
    }
    
    private Task D_Async(string id)
    {
        // This method executes very fast
        return E_Async(id);
    }
    

    In this specific case Index will only await the tasks that E_Async returns. That means that after all the I/O is done the next line of code will directly be return View();. C_Async and D_Async already ran and finished in the synchronous call.

    0 讨论(0)
  • 2021-01-18 14:22

    You must be careful about the thread message pumps and what async really does. The sample below calls into an async method which calls two other async methods which start two tasks to do the actual work which wait 2 and 3 seconds.

    13.00 6520 .ctor Calling async method
    13.00 6520 RunSomethingAsync Before
    13.00 6520 GetSlowString Before
    13.00 5628 OtherTask Sleeping for 2s
    15.00 5628 OtherTask Sleeping done
    15.00 6520 GetVerySlow Inside
    15.00 2176 GetVerySlow Sleeping 3s
    18.00 2176 GetVerySlow Sleeping Done
    18.00 6520 RunSomethingAsync After GetSlowOtherTaskResultGetVerySlowReturn
    

    As you can see the calls are serialized which might not be what you want when you after performance. Perhaps the two distinct await calls do not depend on each other and can be started directly as tasks.

    All methods until GetSlowStringBefore are called on the UI or ASP.NET thread that started the async operation (if it it has a message pump). Only the last call with the result of the operation are marshalled back to the initiating thread.

    The performance penalty is somewhere in the ContextSwitch region to wake up an already existing thread. This should be somewhere at microsecond level. The most expensive stuff would be the creation of the managed objects and the garbage collector cleaning up the temporary objects. If you call this in a tight loop you will be GC bound because there is an upper limit how many threads can be created. In that case TPL will buffer your tasks in queues which require memory allocations and then drain the queues with n worker threads from the thread pool.

    On my Core I7 I get an overhead of 2microseconds for each call (comment out the Debug.Print line) and a memory consumption of 6,5GB for 5 million calls in a WPF application which gives you a memory overhead of 130KB per asynchronous operation chain. If you are after high scalability you need to watch after your GC. Until Joe Duffy has finished his new language we have to use CLR we currently have.

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            Print("Calling async method");
            RunSomethingAsync();
        }
    
        private async void RunSomethingAsync()
        {
            Print("Before");
            string msg = await GetSlowString();
            Print("After " + msg);
            cLabel.Content = msg;
        }
    
        void Print(string message, [CallerMemberName] string method = "")
        {
            Debug.Print("{0:N2} {1} {2} {3}", DateTime.Now.Second, AppDomain.GetCurrentThreadId(), method, message);
        }
    
        private async Task<string> GetSlowString()
        {
            Print("Before");
    
            string otherResult = await OtherTask();
    
            return "GetSlow" + otherResult + await GetVerySlow(); ;
        }
    
        private Task<string> OtherTask()
        {
            return Task.Run(() =>
            {
                Print("Sleeping for 2s");
                Thread.Sleep(2 * 1000);
                Print("Sleeping done");
                return "OtherTaskResult";
            });
        }
    
        private Task<string> GetVerySlow()
        {
            Print("Inside");
            return Task.Run(() =>
            {
                Print("Sleeping 3s");
                Thread.Sleep(3000);
                Print("Sleeping Done");
                return "GetVerySlowReturn";
            });
        }
    }
    
    0 讨论(0)
提交回复
热议问题