27

For microservice functions that simply call an external service or write to a data store, is there any point to using async/await in C#?

We're writing a fair number of these in AWS Lambdas, and it's hard to determine what the actual gain of async/await is in this context or where exactly it would be useful. For more traditional IIS web services, the asynchrony frees up threads in the OS and allows the server to service more requests.

But for AWS Lambdas, the functions only handle a single request per execution (limited to 1000 simultaneous executions). So if we have a long-running external process or external dependency with significant latency, each function execution will be hung up until the external process completes (assuming the Lambda is invoked synchronously).

Here's a sample Lambda with three handlers, the third of which I put into a separate Lambda called "DavidSleep" which simply represents a long-running external dependency. When I invoke a different Lambda called "DavidTest" using either of the first two handlers, I see no functional or performance difference between the async/await version and the one lacking async/await. Both functions require multiple concurrent Lambda executions and take the same amount of time.

So the async version appears to have no difference to the async-less version, but is there any?

public class Test
{
    private IAmazonLambda lambda;

    public Test()
    {
        lambda = new AmazonLambdaClient();
    }

    [LambdaSerializer(typeof(JsonSerializer))]
    public async Task HandleAsync(Request request)
    {
        Console.WriteLine($"Executing for { request.Name }");
        await lambda.InvokeAsync(new InvokeRequest
        {
            FunctionName = "DavidSleep",
            InvocationType = InvocationType.RequestResponse,
            Payload = JsonConvert.SerializeObject(request)
        });
    }

    [LambdaSerializer(typeof(JsonSerializer))]
    public void Handle(Request request)
    {
        Console.WriteLine($"Executing for { request.Name }");
        lambda.InvokeAsync(new InvokeRequest
        {
            FunctionName = "DavidSleep",
            InvocationType = InvocationType.RequestResponse,
            Payload = JsonConvert.SerializeObject(request)
        }).Wait();
    }

    [LambdaSerializer(typeof(JsonSerializer))]
    public void Sleep(Request request)
    {
        Console.WriteLine($"{ request.Name }{ request.RequestId } begin");
        Thread.Sleep(request.WaitInSeconds * 1000);
        Console.WriteLine($"{ request.Name }{ request.RequestId } end");
    }
}
David
  • 287
  • 3
  • 4
  • 5
    Isn't it better to use the async version so that thread is freed up to do something else though? Whatever that may be. – pmcilreavy Jan 05 '18 at 00:59
  • 1
    Async doesn't mean faster. In fact, there is more overhead in async than not but the overall efficiency of the server increases by reuse of threads. I don't know if CPU cycles factor into AWS Lambda cost but if so you might at least save a little money. – Crowcoder Jan 05 '18 at 01:10
  • Your Test runs in console. Where (In what type application web/ console/desktop.. etc...) are your actual lambda calls ? – DaniDev Jan 05 '18 at 01:27
  • No I'm executing these tests remotely. For simplicity I just created one assembly and published it to two lambdas, one caller and one sleeper. Then I invoke the caller lambda using Linqpad so I can run multiple executions simultaneously (just a couple of tasks and a WaitAll()). So theoretically I'm freeing up threads for Amazon to use somewhere else, but not necessarily for my lambda. And *maybe* there's some cost benefit. – David Jan 05 '18 at 01:54
  • 4
    `But for AWS Lambdas, the functions only handle a single request at a time` I'm not familiar with AWS Lambda. Does that mean that a given process will process only one request at a time? If so, then there's no benefit whatsoever in using asynchronicity. As a rule of thumb, if you want to optimize for latency then you should use synchronous code, if you want to optimize for throughput then you should use asynchronous code. If your process can handle only a single request at a time, then throughput isn't really a concern – Kevin Gosse Jan 05 '18 at 07:47
  • That was a bit of a slip-up, but I think your comment still applies. Each _function execution_ only handles a single request, and each AWS account is bound by simultaneous function executions (limited to 1000, which can be upgraded). The asynchronicity doesn't increase the number of executions possible. I'll correct my post. – David Jan 05 '18 at 15:22
  • @David The synchronous version requires a thread to be sitting there doing nothing for the entirety of the operation, and the asynchronous version doesn't. If you're limited to only having 1000 threads at a time, then it *absolutely* affects how much work you can do at any given time. Why would you think that forcing a thread to sit around doing nothing useful for the entirety of every request wouldn't matter to the amount of threads used to handle each request? – Servy Jan 05 '18 at 15:32
  • This is a serverless context, it's not 1000 _threads_ but 1000 _executions_. The executions themselves may occur on the same machine but will more probably be distributed among many machines in the cloud. – David Jan 05 '18 at 15:34
  • In cases when your function load data from different external resources and then combine them to produce a result - then asynchronous approach will make it even faster, because you can send multiple requests and wait for them almost simultaneously with `await Task.WhenAll(request1, request2, request3)` – Fabio Jan 06 '18 at 04:09
  • 1
    https://stackoverflow.com/questions/47348962/aws-lambda-behaviour-when-execution-is-waiting-an-async-operation Similir question of mine – Can Sahin Jan 06 '18 at 18:54
  • I'm currently working on a Lambda which asynchronous was a good stuff. https://docs.aws.amazon.com/lambda/latest/dg/dotnet-programming-model-handler-types.html#dot-net-async In my use case, I'm creating a Custom Resource and I'm getting Timeout from my Lambda. That leads my Stack to stuck creating. I used the above approach to create a Timeout monitor and send back to the stack a timeout fail before Lambda run out in time. – Matheus Maximo Mar 05 '19 at 18:05
  • async / await (syntactic code model sugar over asynchronous code) is really interesting for IO-Bound processing (like using a different hardware processor than the CPU, e.g: network card, etc.). When using a 3rd party library, it's difficult to know unless documented or obvious (like when you use a network stream). See my answer here for more : https://stackoverflow.com/a/40544316/403671 In general, if an async version is provided to me, I use it. – Simon Mourier Dec 19 '20 at 07:03
  • There's an answer about this already: https://stackoverflow.com/questions/37419572/if-async-await-doesnt-create-any-additional-threads-then-how-does-it-make-appl#:~:text=Async%2Fawait%20is%20primarily%20created,t%20create%20it's%20own%20thread. – Mark Rabjohn Dec 21 '20 at 14:09
  • There is immense benefit to the cloud provider - they get to reuse the threads you free up, to service many other customers by recycling the compute. For you, not so much. – StuartLC Dec 23 '20 at 22:20

5 Answers5

2

Benefits of async/await in serverless context would still be that you are yielding control of the calling thread, freeing up another caller to use the thread. I don't know if AWS will remove an await call from the 1000 call limit, but they could potentially.

For these one liner style task calls with no other async calls in the method, you can simply return the Task. Marking the method async and calling await adds un-necessary overhead, regardless of the AWS Lambda 1000 call limit.

Example:

[LambdaSerializer(typeof(JsonSerializer))]
public Task HandleAsync(Request request)
{
    Console.WriteLine($"Executing for { request.Name }");
    return lambda.InvokeAsync(new InvokeRequest
    {
        FunctionName = "DavidSleep",
        InvocationType = InvocationType.RequestResponse,
        Payload = JsonConvert.SerializeObject(request)
    });
}
jjxtra
  • 20,415
  • 16
  • 100
  • 140
1

There are 2 cases discussed here:

  • Async-await in C# code used in the Lambda function
  • Invoke a long-running lambda function asynchronously vs. synchronously

Async/Await pattern in C# code in a Lambda function
The Lambda execution framework does not care of how the code is being executed - it simply invokes the .Net framework to execute the code and waits for the execution to complete. And inside the function, it is the usual async/await pattern advantages.

The Async/await in C# code is useful if you are starting a long operation asynchronously and then using the main thread to do something different which is not dependent on the long operation's result. If there is a single long operation in the function, asynchronous and synchronous executions are similar. No advantage of using async over sync (in fact, as pointed out in the comments above, you may have a slight overhead of using the async mechanism).

Invoke a lambda function asynchronously vs. synchronously
The question describes an example of this difference - a long running lambda is called using an async method and a synchronous method. There is no change observed, for valid reasons.

Again an async call is beneficial only when the main thread can do something different not dependent on the async method's result. The example shows only a call to the lambda. So the main thread has to wait till its execution completes in both async and sync cases. It is not utilizing the wait time for other execution, hence there is no difference seen in the overall time taken for execution.

Scenario for Async
Let's say there are 2 lambda functions, DavidSleep and JohnSleep which need to be invoked in parallel, independent of each other. DavidSleep takes 1 sec to execute and JohnSleep takes 2 sec to execute.

If both these are called in the HandleAsync() above, the total execution time will be ~2 sec (plus a few ms for the async overhead)
If these are called in the Handle() above, the total execution time will be ~3 sec (plus a few ms)

samiksc
  • 167
  • 10
0

Async await is useful when you would like multiple I/O calls parallel. Let's say S3-ReadObject and DynamoDb-GetItem. Fire both then use Task.AwaitAll

Rajeshaz09
  • 348
  • 5
  • 14
-2

Async and await is something that's easily used, but extremely hard to understand. After more than 10 years of programming C# I'm still having issues understanding every mechanic involved. Let me just mention a couple of things: Task.Wait, Task.Run, BlockingCollection, lock statement, ConfigureAwait, CancellationToken, TaskCompletionSource etc

So unless you only deal with highly advanced C# programmers, and are one of those, I would avoid async/await wherever I can. There's a rule that you shouldn't optimize your code too early. If you have something where you need the result of, async/await isn't going to help you anyway, because as soon as you request the result you're still blocked. You can however postpone getting the result by using async/await by having it run in a background thread.

I'm writing all my code synchronously. If I block my UI, then I know I can use async/await to avoid it, because the UI expects me to do it that way. But the UI doesn't show me the result until it's finally processed. That's why I said I'm blocked. Just because the line might be executed in the debugger doesn't mean the result will actually be in the UI.

When I identify a call that's actually blocking me from further processing my data, I would like it to happen in a separated thread, then it of course makes sense to use async/await.

If you have a server, that get's a query, and send the result to the client, there's no need for async/await, because with every magic, and without timetravel, the server won't be able to send a result to the client before it's done.

However, if the server needs to process multiple queries in parallel, and return the results, it makes sense to have the processing of each result in a seperate thread. Therefore you'd like to have asyn/await everywhere where you're processing the result. Otherwise your whole server can be blocked because it's processing a single result while others try to arrive at the same time, but can't be processed because the server is still busy getting the result for the first query.

AdrAs
  • 676
  • 3
  • 14
  • Downvoted because this is a personal rant against `async`. There's merit that synchronous might be better in a serverless context given less overhead, but no evidence given that cpu-spinning is any faster than async, but might have a likely tradeoff against CPU cost in Lambda (my own guess). – Tom Feb 28 '22 at 08:52
-3

Using a async/wait works when you need to do several processes, and the thing (e.g., thread, etc.) making the processes is busy. Additionally, you can make it wait, do it you're way, in the order you want, and stagger multiple executions.

Adam
  • 3,891
  • 3
  • 19
  • 42