J'ai une interface qui expose certaines méthodes asynchrones. Plus spécifiquement, il a des méthodes définies qui retournent soit Task soit Task <T>. J'utilise les mots clés async / await.
Je suis en train d'implémenter cette interface. Cependant, dans certaines de ces méthodes, cette implémentation n'a rien à attendre. Pour cette raison, j'obtiens l'avertissement du compilateur "Cette méthode asynchrone n'a pas d'opérateurs 'await' et s'exécutera de manière synchrone ..."
Je comprends pourquoi j'obtiens l'erreur mais je me demande si je devrais faire quoi que ce soit à leur sujet dans ce contexte. Il semble erroné d'ignorer les avertissements du compilateur.
Je sais que je peux le réparer en attendant un Task.Run, mais cela ne me semble pas correct pour une méthode qui ne fait que quelques opérations peu coûteuses. Il semble également que cela ajoutera une surcharge inutile à l'exécution, mais je ne suis pas non plus sûr que cela soit déjà là car le mot-clé async est présent.
Dois-je simplement ignorer les avertissements ou existe-t-il un moyen de contourner ce problème que je ne vois pas?
la source
async
?async
mot - clé. Vous pouvez toujours renvoyer unTask
fichier usingTask.FromResult
.Task.FromResult
.Réponses:
Le mot clé async est simplement un détail d'implémentation d'une méthode; cela ne fait pas partie de la signature de la méthode. Si une implémentation ou une substitution de méthode particulière n'a rien à attendre, omettez simplement le mot-clé async et renvoyez une tâche terminée à l'aide de Task.FromResult <TResult> :
public Task<string> Foo() // public async Task<string> Foo() { // { Baz(); // Baz(); return Task.FromResult("Hello"); // return "Hello"; } // }
Si votre méthode renvoie Task au lieu de Task <TResult> , vous pouvez renvoyer une tâche terminée de n'importe quel type et valeur.
Task.FromResult(0)
semble être un choix populaire:public Task Bar() // public async Task Bar() { // { Baz(); // Baz(); return Task.FromResult(0); // } // }
Ou, à partir de .NET Framework 4.6, vous pouvez renvoyer Task.CompletedTask :
public Task Bar() // public async Task Bar() { // { Baz(); // Baz(); return Task.CompletedTask; // } // }
la source
await Task.FromResult(0)
? Et pourquoi pasawait Task.Yield()
?async
méthode, vous revenezTask.FromResult(0)
au lieu de l'attendre.Il est parfaitement raisonnable que certaines opérations "asynchrones" se terminent de manière synchrone, tout en étant conformes au modèle d'appel asynchrone pour des raisons de polymorphisme.
Les API d'E / S du système d'exploitation en sont un exemple concret. Les appels asynchrones et superposés sur certains appareils se terminent toujours en ligne (écriture dans un canal implémenté à l'aide de la mémoire partagée, par exemple). Mais ils implémentent la même interface que les opérations en plusieurs parties qui continuent en arrière-plan.
la source
Michael Liu a bien répondu à votre question sur la manière d'éviter l'avertissement: en renvoyant Task.FromResult.
Je vais répondre à la partie «Dois-je m'inquiéter de l'avertissement» de votre question.
La réponse est oui!
La raison en est que l'avertissement se produit fréquemment lorsque vous appelez une méthode qui retourne à l'
Task
intérieur d'une méthode asynchrone sans l'await
opérateur. Je viens de corriger un bogue de concurrence survenu parce que j'ai appelé une opération dans Entity Framework sans attendre l'opération précédente.Si vous pouvez écrire méticuleusement votre code pour éviter les avertissements du compilateur, alors quand il y a un avertissement, il ressortira comme un pouce endolori. J'aurais pu éviter plusieurs heures de débogage.
la source
await
à l'intérieur de la méthode à un endroit (il n'y aura pas de CS1998) mais cela ne signifie pas qu'il n'y aura pas d'autre appel de la méthode asnyc qui manquera de synchronisation (en utilisantawait
ou autre). Maintenant, si quelqu'un souhaite savoir comment s'assurer que vous ne manquez pas la synchronisation accidentellement, assurez-vous simplement de ne pas ignorer un autre avertissement - CS4014. Je recommanderais même de menacer celui-là comme une erreur.Il est peut-être trop tard, mais cela peut être une enquête utile:
Il s'agit de la structure interne du code compilé ( IL ):
public static async Task<int> GetTestData() { return 12; }
il devient en IL:
.method private hidebysig static class [mscorlib]System.Threading.Tasks.Task`1<int32> GetTestData() cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [mscorlib]System.Type) = ( 01 00 28 55 73 61 67 65 4C 69 62 72 61 72 79 2E // ..(UsageLibrary. 53 74 61 72 74 54 79 70 65 2B 3C 47 65 74 54 65 // StartType+<GetTe 73 74 44 61 74 61 3E 64 5F 5F 31 00 00 ) // stData>d__1.. .custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = ( 01 00 00 00 ) // Code size 52 (0x34) .maxstack 2 .locals init ([0] class UsageLibrary.StartType/'<GetTestData>d__1' V_0, [1] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> V_1) IL_0000: newobj instance void UsageLibrary.StartType/'<GetTestData>d__1'::.ctor() IL_0005: stloc.0 IL_0006: ldloc.0 IL_0007: call valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<!0> valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::Create() IL_000c: stfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> UsageLibrary.StartType/'<GetTestData>d__1'::'<>t__builder' IL_0011: ldloc.0 IL_0012: ldc.i4.m1 IL_0013: stfld int32 UsageLibrary.StartType/'<GetTestData>d__1'::'<>1__state' IL_0018: ldloc.0 IL_0019: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> UsageLibrary.StartType/'<GetTestData>d__1'::'<>t__builder' IL_001e: stloc.1 IL_001f: ldloca.s V_1 IL_0021: ldloca.s V_0 IL_0023: call instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::Start<class UsageLibrary.StartType/'<GetTestData>d__1'>(!!0&) IL_0028: ldloc.0 IL_0029: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> UsageLibrary.StartType/'<GetTestData>d__1'::'<>t__builder' IL_002e: call instance class [mscorlib]System.Threading.Tasks.Task`1<!0> valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::get_Task() IL_0033: ret } // end of method StartType::GetTestData
Et sans méthode asynchrone et tâche:
public static int GetTestData() { return 12; }
devient :
.method private hidebysig static int32 GetTestData() cil managed { // Code size 8 (0x8) .maxstack 1 .locals init ([0] int32 V_0) IL_0000: nop IL_0001: ldc.i4.s 12 IL_0003: stloc.0 IL_0004: br.s IL_0006 IL_0006: ldloc.0 IL_0007: ret } // end of method StartType::GetTestData
Comme vous avez pu voir la grande différence entre ces méthodes. Si vous n'utilisez pas la méthode wait inside async et que vous ne vous souciez pas de l'utilisation de la méthode async (par exemple, un appel d'API ou un gestionnaire d'événements), la bonne idée la convertira en méthode de synchronisation normale (cela économise les performances de votre application).
Mis à jour:
Il existe également des informations supplémentaires de Microsoft Docs https://docs.microsoft.com/en-us/dotnet/standard/async-in-depth :
la source
async/await
est extrêmement simplifiée car vous la basez sur votre exemple irréaliste d'une opération unique liée au processeur.Task
s lorsqu'il est utilisé correctement, permet d'améliorer les performances et la réactivité des applications en raison de tâches simultanées (c'est-à-dire parallèles) et d'une meilleure gestion et utilisation des threadsTasks
. C'est triste que vous ne lisiez pas tout le texte du message et que vous ne tiriez pas rapidement des conclusions.int
(comme dans votre cas) et une qui retourneTask
comme discuté par l'OP. Lire son poste et la réponse acceptée à nouveau au lieu de prendre les choses personnellement. Votre réponse n'est pas utile dans ce cas. Vous n'avez même pas la peine de montrer la différence entre une méthode qui a à l'await
intérieur ou non. Maintenant, si vous aviez fait cela, cela aurait valu très bien un vote positifRemarque sur le comportement des exceptions lors du retour
Task.FromResult
Voici une petite démonstration qui montre la différence dans la gestion des exceptions entre les méthodes marquées et non marquées
async
.public Task<string> GetToken1WithoutAsync() => throw new Exception("Ex1!"); // Warning: This async method lacks 'await' operators and will run synchronously. Consider ... public async Task<string> GetToken2WithAsync() => throw new Exception("Ex2!"); public string GetToken3Throws() => throw new Exception("Ex3!"); public async Task<string> GetToken3WithAsync() => await Task.Run(GetToken3Throws); public async Task<string> GetToken4WithAsync() { throw new Exception("Ex4!"); return await Task.FromResult("X");} public static async Task Main(string[] args) { var p = new Program(); try { var task1 = p.GetToken1WithoutAsync(); } catch( Exception ) { Console.WriteLine("Throws before await.");}; var task2 = p.GetToken2WithAsync(); // Does not throw; try { var token2 = await task2; } catch( Exception ) { Console.WriteLine("Throws on await.");}; var task3 = p.GetToken3WithAsync(); // Does not throw; try { var token3 = await task3; } catch( Exception ) { Console.WriteLine("Throws on await.");}; var task4 = p.GetToken4WithAsync(); // Does not throw; try { var token4 = await task4; } catch( Exception ) { Console.WriteLine("Throws on await.");}; }
// .NETCoreApp,Version=v3.0 Throws before await. Throws on await. Throws on await. Throws on await.
(Cross post of my answer for When async Task <T> required by interface, how to get return variable without compiler warning )
la source