27 votes

Existe-t-il une différence entre les lambdas déclarées avec et sans async

Y a-t-il une différence entre les lambdas () => DoSomethingAsync() y async () => await DoSomethingAsync() lorsque les deux sont tapés comme Func<Task> ? Lequel faut-il préférer et quand ?

Voici une application console simple

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"));
        }
    }
}

La sortie est :

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

Ainsi, en RunTheDemo les deux appels à await Runner(someLambda); semblent faire la même chose avec les mêmes caractéristiques de synchronisation - les deux ont un retard correct de deux secondes.

Les deux lignes fonctionnent, alors sont-elles exactement équivalentes ? Quelle est la différence entre les () => DoSomethingAsync() y async () => await DoSomethingAsync() des constructions ? Laquelle faut-il privilégier et quand ?

Il ne s'agit pas de la même question que "dois-je utiliser await dans le cas général", car ici nous avons affaire à du code asynchrone qui fonctionne, avec des lambdas typés en tant que Func<Task> qui sont correctement attendues dans la méthode de consommation. La question est de savoir comment ces lambdas sont déclarées et quels sont les effets de cette déclaration.

20voto

I3arnon Points 9498

Existe-t-il une différence entre les lambdas déclarés avec et sans async

Oui, il y a une différence. L'un est un lambda asynchrone et l'autre est juste un lambda de retour de tâche.

Un lambda asynchrone est compilé dans une machine à états, alors que l'autre ne l'est pas. Un lambda asynchrone a donc une sémantique d'exception différente, car les exceptions sont encapsulées dans la tâche retournée et ne peuvent pas être lancées de manière synchrone.

C'est exactement la même différence que celle qui existe dans les méthodes ordinaires. Par exemple, entre cette méthode asynchrone :

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

Et cette méthode de retour des tâches :

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

L'examen de ces méthodes montre plus clairement les différences, mais comme les lambdas ne sont que du sucre syntaxique, ils sont en fait compilés dans des méthodes qui se comportent exactement de la même manière que celles-ci.

Lequel devons-nous préférer et quand ?

Cela dépend vraiment de vos goûts. L'utilisation du mot-clé async génère une machine à états qui est moins performante que le simple retour d'une tâche. Cependant, la sémantique des exceptions peut être surprenante dans certains cas.

Prenez ce code par exemple :

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

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

Le bloc try-catch traiterait-il le NullReferenceException ou pas ?

Ce ne sera pas le cas, car l'exception est levée de manière synchrone lors de l'appel de la fonction asyncAction . Toutefois, l'exception sera est traitée dans ce cas, car elle est capturée dans la tâche retournée et rejetée à nouveau lorsque cette tâche est attendue.

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

Personnellement, j'utilise des lambdas de retour de tâche pour ces lambdas d'expression d'une ligne, car ils sont généralement assez simples. Mais mon équipe, après quelques bogues extrêmement pénibles, utilise toujours la fonction async y await mots-clés.

4voto

Jacob Sobus Points 651

Voici le résultat de IL Viewer pour ces deux méthodes :

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'

Donc la seconde utilise une machine d'état asynchrone.

3voto

Luaan Points 8934

Oui, ils sont identiques, mais il s'agit d'un exemple assez simple. Les deux sont fonctionnellement équivalents, vous faites juste (éventuellement, selon le compilateur) plus de travail en utilisant async .

Un meilleur argumentaire pour comprendre pourquoi async Les lambdas sont utiles si vous devez traiter une séquence d'opérations asynchrones. await est pour, après tout :

await Runner(async () => await DoSomethingAsync(await httpClient.Get("www.google.com")));

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X