Mistura eficiente de métodos de sincronização e assíncrona em um único método?

11

Ok, parece estranho, mas o código é muito simples e explica bem a situação.

public virtual async Task RemoveFromRoleAsync(AzureTableUser user, string role)
{
    AssertNotDisposed();
    var roles = await GetRolesForUser(user);
    roles.Roles = RemoveRoles(roles.Roles, role);
    await Run(TableOperation.Replace(roles));
}

(Eu sei que estou falando mais ou menos no resumo abaixo, mas o acima é um método real do que será o código de produção real que está realmente fazendo o que estou perguntando aqui e estou realmente interessado em revisar para correção em relação ao padrão assíncrono / espera.)

Estou encontrando esse padrão cada vez mais agora que estou usando async/ awaitmore. O padrão consiste na seguinte cadeia de eventos:

  1. Aguarde uma ligação inicial que receba algumas informações nas quais preciso trabalhar
  2. Trabalhe nessas informações de forma síncrona
  3. Aguarde uma chamada final que salve o trabalho atualizado

O bloco de código acima é geralmente o modo como lidarei com esses métodos. Eu sou awaita primeira chamada, que eu tenho que fazer porque é assíncrona. Em seguida, faço o trabalho necessário, que não é de E / S ou recurso vinculado e, portanto, não é assíncrono. Por fim, guardo meu trabalho, que também é uma asyncligação, e do culto à carga eu awaito faço .

Mas essa é a maneira mais eficiente / correta de lidar com esse padrão? Parece-me que eu poderia pular awaita última ligação, mas e se ela falhar? E devo usar um Taskmétodo como ContinueWithencadear meu trabalho síncrono com a chamada original? Agora estou em um ponto em que não tenho certeza se estou lidando com isso corretamente.

Dado o código no exemplo , existe uma maneira melhor de lidar com essa cadeia de chamadas de método async / sync / async?

Rasgado
fonte
O código me parece o mais curto e compreensível possível. Tudo o que consigo pensar introduz complexidade desnecessária.
Euphoric
@Euphoric: Devo me preocupar em aguardar minha última ligação? O que aconteceria se eu não fizesse e jogasse? Seria diferente como é atualmente?
Arrancado

Respostas:

3

Sim, acho que é o caminho certo para fazê-lo.

Você não pode pular o segundo await. Se você o fizesse, o método pareceria concluir muito cedo (antes da remoção ser realmente concluída) e você nunca descobriria se a remoção falhou.

Não vejo como isso ContinueWith()ou algo assim ajudaria aqui. Você poderia usá-lo para evitar o uso await, mas isso tornaria seu código mais complicado e menos legível. E esse é o objetivo await: simplificar a escrita de código assíncrono, quando comparado ao uso de continuações.

svick
fonte
0

A maneira de lidar com esse padrão é garantir que todas as E / S sejam assíncronas. Os métodos de E / S síncrona fazem com que o encadeamento atual seja bloqueado enquanto aguarda uma resposta do destino de E / S (rede, sistema de arquivos etc.).

Outra coisa a considerar é que awaitdeve ser usado quando você precisar de um valor de retorno ou quando precisar que o código aguardado seja concluído antes de fazer outra coisa. Se você não precisar de nenhuma dessas coisas, pode "disparar e esquecer" seu método assíncrono Task.Run.

Portanto, para o uso mais eficiente dos recursos de computação, se RemoveRoleshouver E / S, ele deve se tornar await RemoveRolesAsynce os métodos de E / S chamados por RemoveRolesAsynctambém devem ser assíncronos (e possivelmente aguardados).

Se o desempenho não for sua maior preocupação, é bom executar algumas E / S síncronas em um encadeamento assíncrono. É uma dívida técnica, no entanto. (Neste caso, você pode querer chamar o primeiro método assíncrono com ConfigureAwait, dependendo de onde o código está sendo executado.)

Aqui está uma visão mais aprofundada das práticas recomendadas - https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

Aqui estão algumas notas sobre o comportamento do ConfigureAwait em diferentes ambientes, como ASP.NET, WebAPI, etc. - /programming/13489065/best-practice-to-call-configureawait-for-all-server-side -código

Wayne Bloss
fonte
2
Você nunca deve disparar e esquecer o código com o Task.Run. Todas as tarefas devem ser aguardadas. Se você não aguardar a Tarefa, e a Tarefa for coletada como lixo em um estado em que a exceção seja "não observada", ela fará com que uma UnobservedTaskException seja gerada no tempo de execução e poderá travar seu aplicativo, dependendo da versão da estrutura.
Triynko