J'ai une interface qui expose certaines méthodes asynchrones. Plus spécifiquement, des méthodes sont définies qui renvoient soit la tâche, soit la tâche <T>. J'utilise les mots clés async/wait.
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, je reçois l'avertissement du compilateur: "Cette méthode asynchrone manque d'opérateurs" en attente "et s'exécutera de manière synchrone ..."
Je comprends pourquoi je reçois l'erreur mais je me demande si je devrais faire quelque chose à leur sujet dans ce contexte. Il est erroné d'ignorer les avertissements du compilateur.
Je sais que je peux résoudre ce problème en attendant une Task.Run, mais cela ne va pas 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 sûr non plus que cela existe déjà car le mot clé async est présent.
Devrais-je simplement ignorer les avertissements ou y a-t-il un moyen de contourner ce problème que je ne vois pas?
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 un remplacement de méthode particulier 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 retourne Task au lieu de Task <TResult> , vous pouvez alors renvoyer une tâche terminée de tout type et de toute 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 retourner Task.CompletedTask :
public Task Bar() // public async Task Bar()
{ // {
Baz(); // Baz();
return Task.CompletedTask; //
} // }
Il est parfaitement raisonnable que certaines opérations "asynchrones" se terminent de manière synchrone, tout en restant conformes au modèle d'appel asynchrone pour des raisons de polymorphisme.
Un exemple concret de ceci est avec les API OS I/O. Les appels asynchrones et superposés sur certains périphériques sont 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 des opérations en plusieurs parties qui continuent en arrière-plan.
Michael Liu a bien répondu à votre question sur la façon d’éviter l’avertissement: en renvoyant Task.FromResult.
Je vais répondre à la partie "Devrais-je m'inquiéter de l'avertissement" de votre question?.
La réponse est oui!
La raison en est que l'avertissement résulte fréquemment lorsque vous appelez une méthode qui renvoie Task
à l'intérieur d'une méthode asynchrone sans l'opérateur await
. Je viens de corriger un bogue d'accès simultané qui s'est produit parce que j'ai appelé une opération dans Entity Framework sans attendre l'opération précédente.
Si vous parvenez à écrire méticuleusement votre code pour éviter les avertissements du compilateur, alors, lorsqu'un avertissement est émis, il se démarque comme un pouce endolori. J'aurais pu éviter plusieurs heures de débogage.
J'ai trouvé un truc pour contourner cet avertissement:
public async Task<object> test()
{
//a pseudo code just to disable the warning about lack of await in async code!
var xyz = true ? 0 : await Task.FromResult(0); //use a var name that's not used later
//... your code statements as normal, eg:
//throw new NotImplementedException();
}
l'existence de ce mot-clé wait permettra au compilateur de ne pas lancer l'avertissement, même si nous savons qu'il ne sera jamais appelé! comme la condition est true
elle retourne toujours la première partie du conditionnel ternaire (? :), et comme cette variable est une variable non utilisatrice, elle sera omise dans les versions Release. Je ne suis pas sûr s'il y a des effets secondaires avec cette approche.
C'est peut-être trop tard, mais ça pourrait être une enquête utile:
Il y a à propos 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 de 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 pouvez voir la grande différence entre ces méthodes. Si vous n'utilisez pas la méthode async en attente et ne vous souciez pas de l'utilisation de la méthode asynchrone (par exemple, un appel d'API ou un gestionnaire d'événements), il est judicieux de la convertir en méthode de synchronisation normale (les performances de votre application seront sauvegardées).