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.
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 .
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 .
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.
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.
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).
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...
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).
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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With