Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

async all the way down issue

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 methods between the controller and the last method in the chain are all marked with the async modifier. Here is an example of how I have the code setup:

public async Task<ActionResult> Index(int[] ids)
{
    List<int> listOfDataPoints = dataPointService(ids);
    List<Task> dpTaskList = new List<Task>();
    foreach (var x in listOfDataPoints)
    {
        dpTaskList.Add(C_Async(x));
    }

    await Task.WhenAll(dpTaskList);
    return View();
}


private async Task C_Async(int id)
{
    //this method executes very fast
    var idTemp = paddID(id);
    await D_Async(idTemp);
}

private async Task D_Async(string id)
{
    //this method executes very fast
    await E_Async(id);
}

private async Task E_Async(string url)
{
    //this method performs the actual async IO
    result = await new WebClient().DownloadStringTaskAsync(new Uri(url))
    saveContent(result);
}

As you can see the controller calls C_Async(x) asynchronously then there is a chain of async methods to E_Async. There are methods between the controller and E_Async and all have the async modifier. Is there a performance penalty since there are methods using the async modifyer but not doing any async IO work?

Note: This is a simplified version of the real code there are more async methods between the controller and the E_Async method.

like image 804
Luke101 Avatar asked Jan 12 '14 07:01

Luke101


People also ask

How can we avoid deadlock in async await?

NET) Avoid Context Switching to Improve Performance. In an application with user interface, the UI should only be updated on a UI thread (context). Other I/O or CPU bound works should be handled by worker threads from thread pool and not by the UI thread.

Is it OK to not await async?

If you forget to use await while calling an async function, the function starts executing. This means that await is not required for executing the function. The async function will return a promise, which you can use later.

What happens when async method is not awaited?

The call to the async method starts an asynchronous task. However, because no Await operator is applied, the program continues without waiting for the task to complete. In most cases, that behavior isn't expected.

Does every async always return promise?

Async functions always return a promise. If the return value of an async function is not explicitly a promise, it will be implicitly wrapped in a promise.


2 Answers

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.

like image 52
i3arnon Avatar answered Oct 21 '22 04:10

i3arnon


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";
        });
    }
}
like image 30
Alois Kraus Avatar answered Oct 21 '22 05:10

Alois Kraus