Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a difference between lambdas declared with and without async

Is there a difference between lambdas () => DoSomethingAsync() and async () => await DoSomethingAsync() when both are typed as Func<Task>? Which one should we prefer and when?

Here is a simple console app

using System; using System.Threading.Tasks;  namespace asyncDemo {     class Program     {         static void Main(string[] args)         {             var demo = new AsyncDemo();             var task = demo.RunTheDemo();             task.Wait();              Console.ReadLine();         }     }      public class AsyncDemo     {          public async Task Runner(Func<Task> action)         {             Console.WriteLine(DateTime.Now.ToLongTimeString() + " Launching the action");             await action();         }          private async Task DoSomethingAsync(string suffix)         {             await Task.Delay(2000);             Console.WriteLine(DateTime.Now.ToLongTimeString() + " Done something, " + suffix);         }          public async Task RunTheDemo()         {             await Runner(() => DoSomethingAsync("no await"));             await Runner(async () => await DoSomethingAsync("with await"));         }     } } 

The output is:

09:31:08 Launching the action 09:31:10 Done something, no await 09:31:10 Launching the action 09:31:12 Done something, with await 

So in RunTheDemo, both calls to await Runner(someLambda); appear to do the same thing with the same timing characteristics - both have the correct two-second delay.

Both lines work, so are they exactly equivalent? What is the difference between the () => DoSomethingAsync() and async () => await DoSomethingAsync() constructs? Which one should we prefer and when?

This is not the same question as "should I use await in the general case", as here we are dealing with working async code, with lambdas typed as Func<Task> which are correctly awaited inside the consuming method. The question concerns how those lambdas are declared, and what the effects of that declaration are.

like image 312
Anthony Avatar asked May 10 '16 08:05

Anthony


People also ask

What is an async lambda?

For asynchronous invocation, Lambda places the event in a queue and returns a success response without additional information. A separate process reads events from the queue and sends them to your function. To invoke a function asynchronously, set the invocation type parameter to Event .

What is lambda expression in C# with example?

The expression num => num * 5 is a lambda expression. The => operator is called the "lambda operator". In this example, num is an input parameter to the anonymous function, and the return value of this function is num * 5 . So when multiplyByFive is called with a parameter of 7 , the result is 7 * 5 , or 35 .

Which is a valid type for this lambda function?

A lambda expression is a function or subroutine without a name that can be used wherever a delegate is valid. Lambda expressions can be functions or subroutines and can be single-line or multi-line. You can pass values from the current scope to a lambda expression. The RemoveHandler statement is an exception.

What is the syntax of defining lambda expression?

A lambda expression is an anonymous function that provides a concise and functional syntax, which is used to write anonymous methods. It is based on the function programming concept and used to create delegates or expression tree types. The syntax is function(arg1, arg2... argn) expression.

What is the difference between a lambda function and a function?

The word "lambda" or "lambda expressions" most often refers to anonymous functions. So in that sense a lambda is a kind of function, but not every function is a lambda (i.e. named functions aren't usually referred to as lambdas).

Can a lambda expression have no name?

Not really, a lambda expression in Scheme is just like a function expression with no name -- but there's nothing stopping you from later giving them a name. For example var f = [function(x){return x;}][0]. You could argue that the function value itself has no name, but that would be true for all functions...

What is the difference between scheme's Lambda and JavaScript's function keywords?

The difference between scheme's lambda keyword and Javascript's function keyword is that the latter can be used to create both anonymous functions and named functions while the former only creates anonymous functions (and you'd use define to create named functions).

What is statement Lambda in JavaScript?

Statement lambda is similar to expression lambda except the statement (s) are enclosed in braces: When we talk about lambda expressions in JavaScript that basically just means using a function as an argument in a call to another function. Show activity on this post.


2 Answers

Is there a difference between lambdas declared with and without async

Yes, there's a difference. One is an async lambda and the other is just a task-returning lambda.

An async lambda is compiled into a state machine while the other doesn't so an async lambda has different exception semantics as exceptions are encapsulated in the returned task and can't be thrown synchronously.

It's exactly the same difference that exists in regular methods. For example between this async method:

async Task FooAsync() {     await DoSomethingAsync("with await"); } 

And this task-returning method:

Task FooAsync() {     return DoSomethingAsync("no await"); } 

Looking at these methods shows the differences more clearly, but as lambdas are just syntactic sugar and are actually compiled into methods that behave just the same as these.

Which one should we prefer and when?

This really depends on your taste. Using the async keyword generates a state machine which is less performant than simply returning a task. However, the exception semantics may be surprising in some cases.

Take this code for example:

Hamster hamster = null; Func<Task> asyncAction = () => FooAsync(hamster.Name);  var task = asyncAction(); try {     await task; } catch {     // handle } 

Would the try-catch block handle the NullReferenceException or not?

It will not because the exception is thrown synchronously when calling asyncAction. However the exception will be handled in this case as it is captured in the returned task and rethrown when that task is awaited.

Func<Task> asyncAction = async () => await FooAsync(hamster.Name); 

I personally use task-returning lambdas for these one line expression lambdas as they're usually pretty simple. But my team, after a few extremely hurtful bugs, always use the async and await keywords.

like image 158
i3arnon Avatar answered Sep 23 '22 10:09

i3arnon


This is the output of IL Viewer for this 2 methods:

await Runner(() => DoSomethingAsync("no await"));      .method private hidebysig instance class [mscorlib]System.Threading.Tasks.Task  '<RunTheDemo>b__5_0'() cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()    = (01 00 00 00 ) .maxstack 8  // [42 32 - 42 60] IL_0000: ldarg.0      // this IL_0001: ldstr        "no await" IL_0006: call         instance class [mscorlib]System.Threading.Tasks.Task TestClass::DoSomethingAsync(string) IL_000b: ret } // end of method CompanyManagementController::'<RunTheDemo>b__5_0'    await Runner(async () => await DoSomethingAsync("with await"));  .method private hidebysig instance class [mscorlib]System.Threading.Tasks.Task  '<RunTheDemo>b__5_1'() cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [mscorlib]System.Type)    = (     01 00 45 57 65 62 43 61 72 64 2e 43 6f 6e 74 72 // ..TestClass     6f 6c 6c 65 72 73 2e 43 6f 6d 70 61 6e 79 4d 61 // +<<RunTheDemo>     6e 61 67 65 6d 65 6e 74 43 6f 6e 74 72 6f 6c 6c // b__5_1>d..     65 72 2b 3c 3c 52 75 6e 54 68 65 44 65 6d 6f 3e       62 5f 5f 35 5f 31 3e 64 00 00                       )   // MetadataClassType(TestClass+<<RunTheDemo>b__5_1>d) .custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor()    = (01 00 00 00 ) .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()    = (01 00 00 00 ) .maxstack 2 .locals init (   [0] class TestClass/'<<RunTheDemo>b__5_1>d' V_0,   [1] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder V_1 )  IL_0000: newobj       instance void TestClass/'<<RunTheDemo>b__5_1>d'::.ctor() IL_0005: stloc.0      // V_0 IL_0006: ldloc.0      // V_0 IL_0007: ldarg.0      // this IL_0008: stfld        class TestClass TestClass/'<<RunTheDemo>b__5_1>d'::'<>4__this' IL_000d: ldloc.0      // V_0 IL_000e: call         valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Create() IL_0013: stfld        valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder TestClass/'<<RunTheDemo>b__5_1>d'::'<>t__builder' IL_0018: ldloc.0      // V_0 IL_0019: ldc.i4.m1     IL_001a: stfld        int32 TestClass/'<<RunTheDemo>b__5_1>d'::'<>1__state' IL_001f: ldloc.0      // V_0 IL_0020: ldfld        valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder TestClass/'<<RunTheDemo>b__5_1>d'::'<>t__builder' IL_0025: stloc.1      // V_1 IL_0026: ldloca.s     V_1 IL_0028: ldloca.s     V_0 IL_002a: call         instance void [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Start<class TestClass/'<<RunTheDemo>b__5_1>d'>(!!0/*class TestClass/'<<RunTheDemo>b__5_1>d'*/&) IL_002f: ldloc.0      // V_0 IL_0030: ldflda       valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder TestClass/'<<RunTheDemo>b__5_1>d'::'<>t__builder' IL_0035: call         instance class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::get_Task() IL_003a: ret } // end of method CompanyManagementController::'<RunTheDemo>b__5_1' 

So second one is using async state machine

like image 28
Jacob Sobus Avatar answered Sep 21 '22 10:09

Jacob Sobus