Aqui está o que eu quero dizer:
public Task<SomeObject> GetSomeObjectByTokenAsync(int id)
{
string token = repository.GetTokenById(id);
if (string.IsNullOrEmpty(token))
{
return Task.FromResult(new SomeObject()
{
IsAuthorized = false
});
}
else
{
return repository.GetSomeObjectByTokenAsync(token).ContinueWith(t =>
{
t.Result.IsAuthorized = true;
return t.Result;
});
}
}
Acima método pode ser esperado e eu acho que se assemelha ao que o T perguntar-based A síncrona P attern sugere fazer? (Os outros padrões que conheço são os padrões APM e EAP .)
Agora, o que acontece com o seguinte código:
public async Task<SomeObject> GetSomeObjectByToken(int id)
{
string token = repository.GetTokenById(id);
if (string.IsNullOrEmpty(token))
{
return new SomeObject()
{
IsAuthorized = false
};
}
else
{
SomeObject result = await repository.GetSomeObjectByTokenAsync(token);
result.IsAuthorized = true;
return result;
}
}
As principais diferenças aqui são que o método é async
e utiliza as await
palavras-chave - então o que isso muda em contraste com o método escrito anteriormente? Eu sei que isso também pode ser esperado. Qualquer método que retorne Task pode, a menos que eu esteja enganado.
Estou ciente da máquina de estado criada com essas instruções switch sempre que um método é rotulado como async
e sei que await
ele próprio não usa thread - ele não bloqueia nada, o thread simplesmente faz outras coisas, até que seja chamado de volta para continuar a execução do código acima.
Mas qual é a diferença subjacente entre os dois métodos, quando os chamamos usando a await
palavra - chave? Existe alguma diferença e, se houver - qual é a preferida?
EDITAR: Eu sinto que o primeiro trecho de código é o preferido, porque eliminamos efetivamente as palavras-chave async / wait, sem nenhuma repercussão - retornamos uma tarefa que continuará sua execução de forma síncrona ou uma tarefa já concluída no caminho ativo (que pode ser em cache).
result.IsAuthorized = true
ele será executado no pool de encadeamentos, enquanto no segundo exemplo, ele poderá ser executado no mesmo encadeamento que foi chamadoGetSomeObjectByToken
(se houvesse umSynchronizationContext
instalado nele, por exemplo, era um encadeamento da interface do usuário). O comportamento seGetSomeObjectByTokenAsync
lança uma exceção também será um pouco diferente. Em geral,await
prefere-seContinueWith
, pois quase sempre é mais legível.Respostas:
O mecanismo
async
/await
faz o compilador transformar seu código em uma máquina de estado. Seu código será executado de forma síncrona até o primeiroawait
que atingir um aguardável que não foi concluído, se houver.No compilador Microsoft C #, essa máquina de estado é um tipo de valor, o que significa que terá um custo muito pequeno quando todas as
await
s forem concluídos aguardáveis, pois não alocará um objeto e, portanto, não gerará lixo. Quando qualquer aguardável não é concluído, esse tipo de valor é inevitavelmente encaixotado.Observe que isso não evita a alocação de
Task
s se esse for o tipo de aguardável usado nasawait
expressões.Com
ContinueWith
, você evita alocações (excetoTask
) se a sua continuação não tiver um fechamento e se você não usar um objeto de estado ou reutilizar um objeto de estado o máximo possível (por exemplo, de um pool).Além disso, a continuação é chamada quando a tarefa é concluída, criando um quadro de pilha, não é embutido. A estrutura tenta evitar estouros de pilha, mas pode haver um caso em que não será evitado, como quando grandes matrizes são alocadas.
A maneira como tenta evitar isso é verificando quanta pilha resta e, se por alguma medida interna a pilha for considerada cheia, ele agendará a continuação para execução no agendador de tarefas. Ele tenta evitar exceções fatais de estouro de pilha ao custo do desempenho.
Aqui está uma diferença sutil entre
async
/await
eContinueWith
:async
/await
agendará continuações,SynchronizationContext.Current
se houver, caso contrário, emTaskScheduler.Current
1ContinueWith
agendará continuações no agendador de tarefas fornecido ou nasTaskScheduler.Current
sobrecargas sem o parâmetro do agendador de tarefasPara simular
async
/await
's comportamento padrão:Para simular
async
/await
's comportamento comTask
' s.ConfigureAwait(false)
:As coisas começam a ficar complicadas com loops e manipulação de exceções. Além de manter seu código legível,
async
/await
funciona com qualquer que seja esperado .Seu caso é melhor tratado com uma abordagem mista: um método síncrono que chama um método assíncrono quando necessário. Um exemplo do seu código com esta abordagem:
Na minha experiência, encontrei muito poucos lugares no código do aplicativo em que adicionar essa complexidade realmente compensa o tempo para desenvolver, revisar e testar essas abordagens, enquanto no código da biblioteca qualquer método pode ser um gargalo.
O único caso em que tendem a executar tarefas é quando um
Task
ouTask<T>
método retorno simplesmente retorna o resultado de outro método assíncrono, sem ele próprio ter executado nenhuma E / S ou pós-processamento.YMMV.
ConfigureAwait(false)
ou espere por alguns que aguardem que usem agendamento personalizadofonte
private sealed class <Main>d__0 : IAsyncStateMachine
. As instâncias dessa classe podem ser reutilizáveis.ContinueWith
?.ContinueWith
deveria ser usado programaticamente. Especialmente se você estiver lidando com tarefas intensivas da CPU em vez de tarefas de E / S. Mas quando você chega ao ponto de ter que lidar comTask
as propriedades de,AggregateException
e com a complexidade das condições de manipulação, ciclos e tratamento de exceções quando outros métodos assíncronos são invocados, dificilmente há motivo para ficar com ela.Task.FromResult("...")
. No código assíncrono, se você armazenar em cache valores por chave que exigem a obtenção de E / S, poderá usar um dicionário em que os valores sejam tarefas, por exemplo, emConcurrentDictionary<string, Task<T>>
vez deConcurrentDictionary<string, T>
usarGetOrAdd
chamada com uma função de fábrica e aguardar o resultado. Isso garante que apenas uma solicitação de E / S seja feita para preencher a chave do cache; ele desperta os garçons assim que a tarefa é concluída e serve como uma tarefa concluída posteriormente.Usando
ContinueWith
você estiver usando as ferramentas que, quando disponível antes da introdução doasync
/await
funcionalidade com C # 5 para trás em 2012. Como uma ferramenta que é detalhado, não é facilmente combináveis, e requer trabalho extra para desembrulharAggregateException
s eTask<Task<TResult>>
valores de retorno (você obter estes quando você passa delegados assíncronos como argumentos). Oferece poucas vantagens em troca. Você pode considerar usá-lo quando desejar anexar várias continuações à mesmaTask
ou, em alguns casos raros, em que não puder usarasync
/await
por algum motivo (como quando você está em um método comout
parâmetros ).Atualização: removi o conselho enganoso que o
ContinueWith
deve usarTaskScheduler.Default
para imitar o comportamento padrão deawait
. Na verdade oawait
por horários predefinidos sua continuação usandoTaskScheduler.Current
.fonte