Existe-t-il une différence conceptuelle entre les deux morceaux de code suivants:
async Task TestAsync()
{
await Task.Run(() => DoSomeWork());
}
et
Task TestAsync()
{
return Task.Run(() => DoSomeWork());
}
Le code généré est-il différent non plus?
EDIT: Pour éviter toute confusion avec Task.Run
, un cas similaire:
async Task TestAsync()
{
await Task.Delay(1000);
}
et
Task TestAsync()
{
return Task.Delay(1000);
}
MISE À JOUR TARDIVE: En plus de la réponse acceptée, il y a aussi une différence dans la façon dont LocalCallContext
est gérée: CallContext.LogicalGetData est restauré même en l'absence d'asynchronie. Pourquoi?
c#
async-await
éviter
la source
la source
await
/async
du tout :)Réponses:
Une différence majeure réside dans la propagation des exceptions. Une exception, jetée à l' intérieur d' une
async Task
méthode, est stockée dans le retourTask
objet et reste en sommeil jusqu'à ce que la tâche s'observe viaawait task
,task.Wait()
,task.Result
outask.GetAwaiter().GetResult()
. Il est propagé de cette manière même s'il est jeté de la partie synchrone de laasync
méthode.Considérez le code suivant, où
OneTestAsync
etAnotherTestAsync
se comportent assez différemment:static async Task OneTestAsync(int n) { await Task.Delay(n); } static Task AnotherTestAsync(int n) { return Task.Delay(n); } // call DoTestAsync with either OneTestAsync or AnotherTestAsync as whatTest static void DoTestAsync(Func<int, Task> whatTest, int n) { Task task = null; try { // start the task task = whatTest(n); // do some other stuff, // while the task is pending Console.Write("Press enter to continue"); Console.ReadLine(); task.Wait(); } catch (Exception ex) { Console.Write("Error: " + ex.Message); } }
Si j'appelle
DoTestAsync(OneTestAsync, -2)
, il produit la sortie suivante:Remarque, j'ai dû appuyer Enterpour le voir.
Maintenant, si j'appelle
DoTestAsync(AnotherTestAsync, -2)
, le flux de travail du code à l'intérieurDoTestAsync
est assez différent, tout comme la sortie. Cette fois, on ne m'a pas demandé d'appuyer sur Enter:Dans les deux cas
Task.Delay(-2)
jette au début, tout en validant ses paramètres. Cela peut être un scénario inventé, mais en théorie, celaTask.Delay(1000)
peut aussi être déclenché, par exemple, lorsque l'API du minuteur système sous-jacent échoue.Par ailleurs, la logique de propagation des erreurs est encore différente pour les
async void
méthodes (par opposition auxasync Task
méthodes). Une exception levée à l'intérieur d'uneasync void
méthode sera immédiatement relancée sur le contexte de synchronisation du thread courant (viaSynchronizationContext.Post
), si le thread actuel en a un (SynchronizationContext.Current != null)
. Sinon, elle sera relancée viaThreadPool.QueueUserWorkItem
). L'appelant n'a pas la possibilité de gérer cette exception sur le même cadre de pile.J'ai publié quelques détails supplémentaires sur le comportement de gestion des exceptions TPL ici et ici .
Q : Est-il possible d'imiter le comportement de propagation des exceptions des
async
méthodes pour les méthodes non asynchronesTask
, de sorte que ces dernières ne lancent pas sur le même cadre de pile?R : Si vraiment nécessaire, alors oui, il y a une astuce pour cela:
// async async Task<int> MethodAsync(int arg) { if (arg < 0) throw new ArgumentException("arg"); // ... return 42 + arg; } // non-async Task<int> MethodAsync(int arg) { var task = new Task<int>(() => { if (arg < 0) throw new ArgumentException("arg"); // ... return 42 + arg; }); task.RunSynchronously(TaskScheduler.Default); return task; }
Notez cependant que dans certaines conditions (comme lorsqu'il est trop profond sur la pile), il
RunSynchronously
peut toujours s'exécuter de manière asynchrone.Une autre différence notable est que la version
async
/await
est plus sujette au blocage dans un contexte de synchronisation autre que celui par défaut . Par exemple, ce qui suit sera verrouillé dans une application WinForms ou WPF:static async Task TestAsync() { await Task.Delay(1000); } void Form_Load(object sender, EventArgs e) { TestAsync().Wait(); // dead-lock here }
Changez-le en une version non asynchrone et il ne sera pas verrouillé:
Task TestAsync() { return Task.Delay(1000); }
La nature de l'impasse est bien expliquée par Stephen Cleary dans son blog .
la source
return Task.Run()
etawait Task.Run(); return
, plutôt queawait Task.Run().ConfigureAwait(false); return
Je suis confus par cette question. Permettez-moi d'essayer de clarifier en répondant à votre question par une autre question. Quelle est la différence entre?
Func<int> MakeFunction() { Func<int> f = ()=>1; return ()=>f(); }
et
Func<int> MakeFunction() { return ()=>1; }
?
Quelle que soit la différence entre mes deux choses, la même différence existe entre vos deux choses.
la source
Task.Delay(1000).ContinueWith(() = {})
. Dans le second, c'est justeTask.Delay(1000)
. La différence est quelque peu subtile, mais significative.La première méthode ne compile même pas.
Ça doit être
async Task TestAsync() { await Task.Run(() => DoSomeWork()); }
Il existe une différence conceptuelle majeure entre les deux. Le premier est asynchrone, le second ne l'est pas. Lisez Async Performance: Understanding the Costs of Async and Await pour en savoir plus sur les composants internes de
async
/await
.Ils génèrent un code différent.
.method private hidebysig instance class [mscorlib]System.Threading.Tasks.Task TestAsync () cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [mscorlib]System.Type) = ( 01 00 25 53 4f 54 65 73 74 50 72 6f 6a 65 63 74 2e 50 72 6f 67 72 61 6d 2b 3c 54 65 73 74 41 73 79 6e 63 3e 64 5f 5f 31 00 00 ) .custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = ( 01 00 00 00 ) // Method begins at RVA 0x216c // Code size 62 (0x3e) .maxstack 2 .locals init ( [0] valuetype SOTestProject.Program/'<TestAsync>d__1', [1] class [mscorlib]System.Threading.Tasks.Task, [2] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder ) IL_0000: ldloca.s 0 IL_0002: ldarg.0 IL_0003: stfld class SOTestProject.Program SOTestProject.Program/'<TestAsync>d__1'::'<>4__this' IL_0008: ldloca.s 0 IL_000a: call valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Create() IL_000f: stfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'<TestAsync>d__1'::'<>t__builder' IL_0014: ldloca.s 0 IL_0016: ldc.i4.m1 IL_0017: stfld int32 SOTestProject.Program/'<TestAsync>d__1'::'<>1__state' IL_001c: ldloca.s 0 IL_001e: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'<TestAsync>d__1'::'<>t__builder' IL_0023: stloc.2 IL_0024: ldloca.s 2 IL_0026: ldloca.s 0 IL_0028: call instance void [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::Start<valuetype SOTestProject.Program/'<TestAsync>d__1'>(!!0&) IL_002d: ldloca.s 0 IL_002f: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder SOTestProject.Program/'<TestAsync>d__1'::'<>t__builder' IL_0034: call instance class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder::get_Task() IL_0039: stloc.1 IL_003a: br.s IL_003c IL_003c: ldloc.1 IL_003d: ret } // end of method Program::TestAsync
et
.method private hidebysig instance class [mscorlib]System.Threading.Tasks.Task TestAsync2 () cil managed { // Method begins at RVA 0x21d8 // Code size 23 (0x17) .maxstack 2 .locals init ( [0] class [mscorlib]System.Threading.Tasks.Task CS$1$0000 ) IL_0000: nop IL_0001: ldarg.0 IL_0002: ldftn instance class [mscorlib]System.Threading.Tasks.Task SOTestProject.Program::'<TestAsync2>b__4'() IL_0008: newobj instance void class [mscorlib]System.Func`1<class [mscorlib]System.Threading.Tasks.Task>::.ctor(object, native int) IL_000d: call class [mscorlib]System.Threading.Tasks.Task [mscorlib]System.Threading.Tasks.Task::Run(class [mscorlib]System.Func`1<class [mscorlib]System.Threading.Tasks.Task>) IL_0012: stloc.0 IL_0013: br.s IL_0015 IL_0015: ldloc.0 IL_0016: ret } // end of method Program::TestAsync2
la source
Les deux exemples ne diffèrent. Lorsqu'une méthode est marquée avec le
async
mot - clé, le compilateur génère une machine d'état dans les coulisses. C'est ce qui est responsable de la reprise des continuations une fois qu'un attendu a été attendu.En revanche, lorsqu'une méthode n'est pas marquée avec,
async
vous perdez la capacité desawait
Waitables. (C'est-à-dire dans la méthode elle-même; la méthode peut toujours être attendue par son appelant.) Cependant, en évitant leasync
mot - clé, vous ne générez plus la machine à états, ce qui peut ajouter un peu de surcharge (lever les locaux vers les champs de la machine à états, des objets supplémentaires au GC).Dans des exemples comme celui-ci, si vous êtes en mesure d'éviter
async-await
et de renvoyer directement un fichier attendu, cela doit être fait pour améliorer l'efficacité de la méthode.Voir cette question et cette réponse qui sont très similaires à votre question et à cette réponse.
la source