Tenho uma interface que expõe alguns métodos assíncronos. Mais especificamente, ele possui métodos definidos que retornam Task ou Task <T>. Estou usando as palavras-chave async / await.
Estou implementando esta interface. No entanto, em alguns desses métodos, essa implementação não tem nada a esperar. Por esse motivo, estou recebendo o aviso do compilador "Este método assíncrono não tem operadores 'em espera' e será executado de forma síncrona ..."
Eu entendo por que estou recebendo o erro, mas me pergunto se devo fazer algo a respeito neste contexto. Parece errado ignorar os avisos do compilador.
Eu sei que posso consertá-lo aguardando em um Task.Run, mas isso parece errado para um método que faz apenas algumas operações baratas. Também parece que vai adicionar sobrecarga desnecessária à execução, mas também não tenho certeza se isso já está lá porque a palavra-chave async está presente.
Devo simplesmente ignorar os avisos ou há uma maneira de contornar isso que não estou vendo?
fonte
async
?async
palavra - chave. Você ainda pode devolver umTask
usandoTask.FromResult
.Task.FromResult
.Respostas:
A palavra - chave async é apenas um detalhe de implementação de um método; não faz parte da assinatura do método. Se a implementação ou substituição de um método específico não tiver nada a esperar, basta omitir a palavra - chave async e retornar uma tarefa concluída usando Task.FromResult <TResult> :
public Task<string> Foo() // public async Task<string> Foo() { // { Baz(); // Baz(); return Task.FromResult("Hello"); // return "Hello"; } // }
Se o seu método retornar Task em vez de Task <TResult> , você poderá retornar uma tarefa concluída de qualquer tipo e valor.
Task.FromResult(0)
parece ser uma escolha popular:public Task Bar() // public async Task Bar() { // { Baz(); // Baz(); return Task.FromResult(0); // } // }
Ou, a partir do .NET Framework 4.6, você pode retornar Task.CompletedTask :
public Task Bar() // public async Task Bar() { // { Baz(); // Baz(); return Task.CompletedTask; // } // }
fonte
await Task.FromResult(0)
? Que talawait Task.Yield()
?async
método, você retorna aoTask.FromResult(0)
invés de esperar.É perfeitamente razoável que algumas operações "assíncronas" sejam concluídas de forma síncrona, embora ainda estejam em conformidade com o modelo de chamada assíncrona por causa do polimorfismo.
Um exemplo real disso é com as APIs de E / S do sistema operacional. As chamadas assíncronas e sobrepostas em alguns dispositivos sempre são concluídas inline (gravação em um pipe implementado usando memória compartilhada, por exemplo). Mas eles implementam a mesma interface que as operações de várias partes que continuam em segundo plano.
fonte
Michael Liu respondeu bem à sua pergunta sobre como você pode evitar o aviso: retornando Task.FromResult.
Vou responder à parte "Devo me preocupar com o aviso" da sua pergunta.
A resposta é sim!
A razão para isso é que o aviso freqüentemente ocorre quando você chama um método que retorna
Task
dentro de um método assíncrono sem oawait
operador. Acabei de corrigir um bug de simultaneidade que acontecia porque invoquei uma operação no Entity Framework sem esperar a operação anterior.Se você puder escrever seu código meticulosamente para evitar avisos do compilador, então quando houver um aviso, ele se destacará como um polegar dolorido. Eu poderia ter evitado várias horas de depuração.
fonte
await
dentro do método em um lugar (não haverá CS1998), mas isso não significa que não haverá nenhuma outra chamada do método asnyc que não terá a sincronização (usandoawait
ou qualquer outro). Agora, se alguém quiser saber como ter certeza de que você não perderá a sincronização acidentalmente, certifique-se de não ignorar outro aviso - CS4014. Eu até recomendaria ameaçar isso como um erro.Pode ser tarde demais, mas pode ser uma investigação útil:
Há sobre a estrutura interna do código compilado ( IL ):
public static async Task<int> GetTestData() { return 12; }
torna-se em 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
E sem método assíncrono e tarefa:
public static int GetTestData() { return 12; }
torna-se :
.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
Como você pode ver a grande diferença entre esses métodos. Se você não usar await dentro do método assíncrono e não se preocupa em usar o método assíncrono (por exemplo, chamada de API ou manipulador de eventos), a boa ideia irá convertê-lo para o método de sincronização normal (ele salva o desempenho do seu aplicativo).
Atualizada:
Também há informações adicionais nos documentos da microsoft https://docs.microsoft.com/en-us/dotnet/standard/async-in-depth :
fonte
async/await
é extremamente simplificada, pois você a está baseando em seu exemplo irreal de uma única operação que é limitada pela CPU.Task
s, quando usado corretamente, permite melhor desempenho do aplicativo e capacidade de resposta devido a tarefas simultâneas (ou seja, paralelas) e melhor gerenciamento e uso de threadsTasks
. É uma história triste que você não esteja lendo o texto inteiro do post e tirando conclusões rapidamente.int
(como no seu caso) e um que retornaTask
como discutido pelo OP. Leia a postagem e a resposta aceita novamente, em vez de levar as coisas para o lado pessoal. Sua resposta não é útil neste caso. Você nem se preocupa em mostrar a diferença entre um método que temawait
dentro ou não. Agora você tivesse feito isso, que teria sido muito bom vale a pena uma upvoteNota sobre o comportamento da exceção ao retornar
Task.FromResult
Aqui está uma pequena demonstração que mostra a diferença no tratamento de exceções entre métodos marcados e não marcados com
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.
(Postagem cruzada da minha resposta para Quando a tarefa assíncrona <T> exigida pela interface, como obter a variável de retorno sem aviso do compilador )
fonte