c# - pass - Gibt es einen Unterschied zwischen Lambdas mit und ohne Async deklariert?




linq lambda select (2)

Gibt es einen Unterschied zwischen Lambdas mit und ohne Async deklariert?

Ja, es gibt einen Unterschied. Der eine ist ein Async-Lambda und der andere ist nur ein Task-Return-Lambda.

Ein asynchrones Lambda wird in eine Zustandsmaschine kompiliert, während das andere nicht async-lambda eine andere Ausnahmesemantik hat, da Ausnahmen in der zurückgegebenen Aufgabe eingekapselt sind und nicht synchron geworfen werden können.

Es ist genau der gleiche Unterschied wie bei normalen Methoden. Zum Beispiel zwischen dieser asynchronen Methode:

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

Und diese Aufgabe zurückgebende Methode:

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

Der Blick auf diese Methoden zeigt die Unterschiede deutlicher, aber da Lambdas nur syntaktischer Zucker sind und tatsächlich in Methoden kompiliert werden, die sich genauso verhalten wie diese.

Welches sollten wir bevorzugen und wann?

Das hängt wirklich von deinem Geschmack ab. Wenn Sie das Schlüsselwort async verwenden, wird eine Zustandsmaschine generiert, die weniger performant ist als das einfache Zurückgeben einer Aufgabe. Die Ausnahmesemantik kann jedoch in einigen Fällen überraschend sein.

Nimm diesen Code zum Beispiel:

Hamster hamster = null;
Func<Task> asyncAction = () => FooAsync(hamster.Name);

var task = asyncAction();
try
{
    await task;
}
catch
{
    // handle
}

Würde der try-catch-Block die NullReferenceException oder nicht?

asyncAction nicht, da die Ausnahme beim Aufruf von asyncAction synchron asyncAction . Die Ausnahme wird in diesem Fall jedoch behandelt, da sie in der zurückgegebenen Task erfasst und erneut gestartet wird, wenn diese Task erwartet wird.

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

Ich persönlich benutze Aufgaben-zurückkehrende Lambdas für diese Ein-Zeilen-Expression-Lambdas, da sie normalerweise ziemlich einfach sind. Aber mein Team verwendet nach ein paar extrem schmerzhaften Fehlern immer die async und await Schlüsselwörter.

https://code.i-harness.com

Gibt es einen Unterschied zwischen lambdas () => DoSomethingAsync() und async () => await DoSomethingAsync() wenn beide als Func<Task> eingegeben werden? Welches sollten wir bevorzugen und wann?

Hier ist eine einfache Konsolen-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"));
        }
    }
}

Die Ausgabe ist:

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

Also in RunTheDemo , beide Anrufe await Runner(someLambda); scheinen dasselbe mit den gleichen Timing-Eigenschaften zu tun - beide haben die richtige Verzögerung von zwei Sekunden.

Beide Linien funktionieren, also sind sie genau gleichwertig? Was ist der Unterschied zwischen den () => DoSomethingAsync() und async () => await DoSomethingAsync() Konstrukten? Welches sollten wir bevorzugen und wann?

Dies ist nicht die gleiche Frage wie "sollte ich im allgemeinen Fall await ", da es sich hier um asynchronen Code handelt, mit Lambdas, die als Func<Task> typisiert sind und die in der konsumierenden Methode korrekt erwartet werden. Die Frage betrifft, wie diese lambdas erklärt werden und welche Auswirkungen diese Erklärung hat.


Dies ist die Ausgabe von IL Viewer für diese 2 Methoden:

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 zweitens verwendet asynchrone Zustandsmaschine





async-await