Existe alguma diferença conceitual entre os dois trechos de código a seguir:
async Task TestAsync()
{
await Task.Run(() => DoSomeWork());
}
e
Task TestAsync()
{
return Task.Run(() => DoSomeWork());
}
O código gerado é diferente?
EDITAR: Para evitar confusão com Task.Run
, um caso semelhante:
async Task TestAsync()
{
await Task.Delay(1000);
}
e
Task TestAsync()
{
return Task.Delay(1000);
}
ATRASO: Além da resposta aceita, também há uma diferença em como LocalCallContext
é tratado: CallContext.LogicalGetData é restaurado mesmo onde não há assincronia. Por quê?
c#
async-await
evitar
fonte
fonte
await
/async
em absoluto :)Respostas:
Uma das principais diferenças está na propagação de exceções. Uma excepção, jogado dentro de um
async Task
método, é armazenado na devolvidoTask
objecto e permanece dormente até que a tarefa fica observada através deawait task
,task.Wait()
,task.Result
outask.GetAwaiter().GetResult()
. Ele é propagado dessa forma, mesmo se lançado da parte síncrona doasync
método.Considere o seguinte código, onde
OneTestAsync
eAnotherTestAsync
se comporta de maneira bastante diferente: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); } }
Se eu chamar
DoTestAsync(OneTestAsync, -2)
, ele produzirá a seguinte saída:Note, eu tive que pressionar Enterpara ver.
Agora, se eu chamar
DoTestAsync(AnotherTestAsync, -2)
, o fluxo de trabalho do código internoDoTestAsync
é bastante diferente, assim como a saída. Desta vez, não fui solicitado a pressionar Enter:Em ambos os casos
Task.Delay(-2)
joga no início, ao validar seus parâmetros. Este pode ser um cenário inventado, mas em teoriaTask.Delay(1000)
pode lançar também, por exemplo, quando a API do cronômetro do sistema subjacente falha.Em uma nota lateral, a lógica de propagação de erro ainda é diferente para
async void
métodos (em oposição aosasync Task
métodos). Uma exceção levantada dentro de umasync void
método será imediatamente relançada no contexto de sincronização do thread atual (viaSynchronizationContext.Post
), se o thread atual tiver um (SynchronizationContext.Current != null)
. Caso contrário, ela será relançada viaThreadPool.QueueUserWorkItem
). O chamador não tem chance de lidar com essa exceção no mesmo quadro de pilha.Publiquei mais alguns detalhes sobre o comportamento de tratamento de exceções TPL aqui e aqui .
P : É possível simular o comportamento de propagação de exceções de
async
métodos para métodos não-assíncronosTask
, de modo que o último não atinja o mesmo frame de pilha?R : Se realmente necessário, então sim, há um truque para isso:
// 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; }
No entanto, observe que, sob certas condições (como quando é muito profundo na pilha),
RunSynchronously
ainda pode ser executado de forma assíncrona.Outra diferença notável é que a versão
async
/await
é mais propensa a travamento em um contexto de sincronização não padrão . Por exemplo, o seguinte irá travar em um aplicativo WinForms ou WPF:static async Task TestAsync() { await Task.Delay(1000); } void Form_Load(object sender, EventArgs e) { TestAsync().Wait(); // dead-lock here }
Altere para uma versão não assíncrona e não bloqueará:
Task TestAsync() { return Task.Delay(1000); }
A natureza do bloqueio é bem explicada por Stephen Cleary em seu blog .
fonte
return Task.Run()
eawait Task.Run(); return
, em vez deawait Task.Run().ConfigureAwait(false); return
Estou confuso com esta pergunta. Deixe-me tentar esclarecer respondendo à sua pergunta com outra pergunta. Qual é a diferença entre?
Func<int> MakeFunction() { Func<int> f = ()=>1; return ()=>f(); }
e
Func<int> MakeFunction() { return ()=>1; }
?
Qualquer que seja a diferença entre minhas duas coisas, a mesma diferença é entre as suas duas coisas.
fonte
Task.Delay(1000).ContinueWith(() = {})
. No segundo, é sóTask.Delay(1000)
. A diferença é um tanto sutil, mas significativa.O primeiro método nem mesmo compila.
Tem que ser
async Task TestAsync() { await Task.Run(() => DoSomeWork()); }
Há uma grande diferença conceitual entre esses dois. O primeiro é assíncrono, o segundo não. Leia Desempenho do Async: Compreendendo os custos do Async e Await para saber um pouco mais sobre os aspectos internos de
async
/await
.Eles geram códigos diferentes.
.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
e
.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
fonte
Os dois exemplos são diferentes. Quando um método é marcado com a
async
palavra - chave, o compilador gera uma máquina de estado nos bastidores. Este é o responsável por retomar as continuações uma vez que um aguardado tenha sido aguardado.Em contraste, quando um método não é marcado com
async
você está perdendo a capacidade deawait
esperar. (Ou seja, dentro do próprio método; o método ainda pode ser aguardado por seu chamador.) No entanto, evitando oasync
palavra chave, você não está mais gerando a máquina de estado, que pode adicionar um pouco de sobrecarga (elevar locais para campos da máquina de estado, objetos adicionais ao GC).Em exemplos como este, se você for capaz de evitar
async-await
e retornar um objeto de espera diretamente, isso deve ser feito para melhorar a eficiência do método.Veja esta pergunta e esta resposta que são muito semelhantes à sua pergunta e esta resposta.
fonte