Como chamar com segurança um método assíncrono em C # sem aguardar

322

Eu tenho um asyncmétodo que não retorna dados:

public async Task MyAsyncMethod()
{
    // do some stuff async, don't return any data
}

Estou chamando isso de outro método que retorna alguns dados:

public string GetStringData()
{
    MyAsyncMethod(); // this generates a warning and swallows exceptions
    return "hello world";
}

Ligar MyAsyncMethod()sem aguardar causa um aviso " Como essa chamada não é aguardada, o método atual continua a ser executado antes que a chamada seja concluída " no visual studio. Na página desse aviso, ele declara:

Você deve suprimir o aviso apenas se tiver certeza de que não deseja aguardar a conclusão da chamada assíncrona e de que o método chamado não apresentará exceções .

Tenho certeza de que não quero esperar a ligação terminar; Eu não preciso ou tenho tempo para isso. Mas a ligação pode gerar exceções.

Eu me deparei com esse problema algumas vezes e tenho certeza de que é um problema comum que deve ter uma solução comum.

Como chamo com segurança um método assíncrono sem aguardar o resultado?

Atualizar:

Para as pessoas que sugerem que eu apenas espero o resultado, esse é um código que está respondendo a uma solicitação da Web em nosso serviço da Web (API da Web do ASP.NET). Aguardar em um contexto de interface do usuário mantém o encadeamento da interface do usuário livre, mas aguardar em uma chamada de solicitação da Web aguardará a conclusão da tarefa antes de responder à solicitação, aumentando assim os tempos de resposta sem motivo.

George Powell
fonte
Por que não criar um método de conclusão e ignorá-lo lá? Porque se estiver sendo executado no thread de segundo plano. Então, ele não impedirá que seu programa seja encerrado.
Yahya
1
Se você não quiser esperar pelo resultado, a única opção é ignorar / suprimir o aviso. Se você não quiser esperar para o resultado / exceção, entãoMyAsyncMethod().Wait()
Peter Ritchie
1
Sobre sua edição: isso não faz sentido para mim. Digamos que a resposta seja enviada ao cliente 1 segundo após a solicitação e 2 segundos depois, o método assíncrono gera uma exceção. O que você faria com essa exceção? Você não pode enviá-lo para o cliente, se sua resposta já tiver sido enviada. O que mais você faria com isso?
2
@ Romoku Fair o suficiente. Supondo que alguém veja o log de qualquer maneira. :)
2
Uma variação no cenário da API da Web do ASP.NET é uma API da Web auto-hospedada em um processo de longa duração (como, por exemplo, um serviço do Windows), em que uma solicitação cria uma longa tarefa em segundo plano para fazer algo caro, mas ainda deseja obtenha uma resposta rapidamente com um HTTP 202 (Aceito).
David Rubin

Respostas:

187

Se você deseja obter a exceção "de forma assíncrona", você pode:

  MyAsyncMethod().
    ContinueWith(t => Console.WriteLine(t.Exception),
        TaskContinuationOptions.OnlyOnFaulted);

Isso permitirá que você lide com uma exceção em um segmento que não seja o segmento "principal". Isso significa que você não precisa "esperar" a chamada MyAsyncMethod()do thread que chama MyAsyncMethod; mas ainda permite que você faça algo com uma exceção - mas somente se ocorrer uma exceção.

Atualizar:

tecnicamente, você poderia fazer algo semelhante com await:

try
{
    await MyAsyncMethod().ConfigureAwait(false);
}
catch (Exception ex)
{
    Trace.WriteLine(ex);
}

... o que seria útil se você precisasse usar especificamente try/ catch(ou using), mas acho ContinueWithque isso é um pouco mais explícito, porque você precisa saber o que ConfigureAwait(false)significa.

Peter Ritchie
fonte
7
Transformei isso em um método de extensão Task: public static class AsyncUtility {public static void PerformAsyncTaskWithoutAwait (esta tarefa, Action <Task> exceptionHandler) {var dummy = task.ContinueWith (t => exceptionHandler (t), TaskContinuationOptions.OnlyOnFaulted); }} Uso: MyAsyncMethod (). PerformAsyncTaskWithoutAwait (t => log.ErrorFormat ("Ocorreu um erro ao chamar MyAsyncMethod: \ n {0}", t.Exception));
Mark Avenius
2
downvoter. Comentários? Se houver algo errado na resposta, eu adoraria saber e / ou corrigir.
Peter Ritchie
2
Ei - não diminuí a votação, mas ... Você pode explicar sua atualização? A chamada não deveria ser esperado, por isso, se você fazê-lo assim, então você espera da chamada, você simplesmente não continuar no contexto capturado ...
Bartosz
1
"wait" não é a descrição correta neste contexto. A linha após o ConfiguratAwait(false)não é executada até que a tarefa seja concluída, mas o segmento atual não "espera" (isto é, bloco) por isso, a próxima linha é chamada de forma assíncrona para a chamada em espera. Sem a ConfigureAwait(false)próxima linha, ela seria executada no contexto original da solicitação da web. Com o ConfigurateAwait(false)ele é executado no mesmo contexto como o método assíncrono (tarefa), liberando o original contexto / thread para continuar ...
Peter Ritchie
15
A versão ContinueWith não é a mesma que a versão try {waitit catch {}. Na primeira versão, tudo depois de ContinueWith () será executado imediatamente. A tarefa inicial é acionada e esquecida. Na segunda versão, tudo após a captura {} será executado somente após a conclusão da tarefa inicial. A segunda versão é equivalente a "MyAsyncMethod await () ContinueWith (t => Console.WriteLine (t.Exception), TaskContinuationOptions.OnlyOnFaulted) .ConfigureAwait (Fals);.
Thanasis Ioannidis
67

Você deve primeiro pensar em criar GetStringDataum asyncmétodo e fazer com que awaita tarefa seja retornada MyAsyncMethod.

Se tiver certeza absoluta de que não precisa lidar com exceções MyAsyncMethod ou saber quando elas são concluídas, faça o seguinte:

public string GetStringData()
{
  var _ = MyAsyncMethod();
  return "hello world";
}

BTW, este não é um "problema comum". É muito raro querer executar algum código e não se importar se é concluído ou não, se é concluído com êxito.

Atualizar:

Como você está no ASP.NET e deseja retornar mais cedo, você pode achar útil minha postagem no blog sobre o assunto . No entanto, o ASP.NET não foi projetado para isso e não há garantia de que seu código será executado após o retorno da resposta. O ASP.NET fará o possível para deixá-lo funcionar, mas não pode garantir.

Portanto, esta é uma boa solução para algo simples, como lançar um evento em um log, onde realmente não importa se você perder alguns aqui e ali. Não é uma boa solução para qualquer tipo de operações críticas aos negócios. Nessas situações, você deve adotar uma arquitetura mais complexa, com uma maneira persistente de salvar as operações (por exemplo, Filas do Azure, MSMQ) e um processo em segundo plano separado (por exemplo, Função de Trabalhador do Azure, Serviço Win32) para processá-las.

Stephen Cleary
fonte
2
Eu acho que você pode ter entendido errado. Eu faço cuidado se ele lança exceções e falhar, mas eu não quero ter que aguardar o método antes de retornar meus dados. Veja também minha edição sobre o contexto em que estou trabalhando, se isso faz alguma diferença.
George Powell
5
@ GeorgePowell: É muito perigoso ter código em execução no contexto do ASP.NET sem uma solicitação ativa. Eu tenho um post de blog que pode ajudá-lo , mas sem saber mais sobre o seu problema, não sei dizer se recomendo essa abordagem ou não.
Stephen Cleary
@StephenCleary Tenho uma necessidade semelhante. No meu exemplo, eu tenho / preciso de um mecanismo de processamento em lote para executar na nuvem, "ping" o ponto final para iniciar o processamento em lote, mas desejo retornar imediatamente. Como o ping inicia, ele pode lidar com tudo a partir daí. Se existem exceções que são lançadas, então eles tinham acabado de ser registrado no meu "BatchProcessLog / erro" mesas ...
Ganders
8
No C # 7, você pode substituir var _ = MyAsyncMethod();por _ = MyAsyncMethod();. Isso ainda evita o aviso CS4014, mas torna um pouco mais explícito que você não está usando a variável.
Brian
48

A resposta de Peter Ritchie era o que eu queria, e o artigo de Stephen Cleary sobre retornar cedo no ASP.NET foi muito útil.

No entanto, como um problema mais geral (não específico ao contexto do ASP.NET), o aplicativo Console a seguir demonstra o uso e o comportamento da resposta de Peter usando Task.ContinueWith(...)

static void Main(string[] args)
{
  try
  {
    // output "hello world" as method returns early
    Console.WriteLine(GetStringData());
  }
  catch
  {
    // Exception is NOT caught here
  }
  Console.ReadLine();
}

public static string GetStringData()
{
  MyAsyncMethod().ContinueWith(OnMyAsyncMethodFailed, TaskContinuationOptions.OnlyOnFaulted);
  return "hello world";
}

public static async Task MyAsyncMethod()
{
  await Task.Run(() => { throw new Exception("thrown on background thread"); });
}

public static void OnMyAsyncMethodFailed(Task task)
{
  Exception ex = task.Exception;
  // Deal with exceptions here however you want
}

GetStringData()retornos início sem aguardar MyAsyncMethod()e exceções lançadas em MyAsyncMethod()são tratados nos OnMyAsyncMethodFailed(Task task)e não na try/ catchem torno deGetStringData()

George Powell
fonte
9
Remova Console.ReadLine();e adicione um pouco de sono / atraso MyAsyncMethode você nunca verá a exceção.
precisa saber é
17

Termino com esta solução:

public async Task MyAsyncMethod()
{
    // do some stuff async, don't return any data
}

public string GetStringData()
{
    // Run async, no warning, exception are catched
    RunAsync(MyAsyncMethod()); 
    return "hello world";
}

private void RunAsync(Task task)
{
    task.ContinueWith(t =>
    {
        ILog log = ServiceLocator.Current.GetInstance<ILog>();
        log.Error("Unexpected Error", t.Exception);

    }, TaskContinuationOptions.OnlyOnFaulted);
}
Filimindji
fonte
8

Isso é chamado de fogo e esqueça, e há uma extensão para isso.

Consome uma tarefa e não faz nada com ela. Útil para chamadas disparar e esquecer para métodos assíncronos dentro de métodos assíncronos.

Instale o pacote nuget .

Usar:

MyAsyncMethod().Forget();
desperdício
fonte
12
Não faz nada, é apenas um truque para remover o aviso. Veja github.com/microsoft/vs-threading/blob/master/src/…
Paolo Fulgoni
2

Acho que surge a pergunta, por que você precisaria fazer isso? O motivo para o asyncC # 5.0 é que você pode aguardar um resultado. Esse método não é realmente assíncrono, mas simplesmente chamado de cada vez, para não interferir muito no encadeamento atual.

Talvez seja melhor iniciar um thread e deixá-lo terminar por conta própria.

rhughes
fonte
2
asyncé um pouco mais do que apenas "aguardar" um resultado. "aguardar" implica que as linhas após "aguardar" sejam executadas de forma assíncrona no mesmo encadeamento que chamou "aguardar". Isso pode ser feito sem "aguardar", é claro, mas você acaba tendo vários delegados e perde a aparência sequencial do código (assim como a capacidade de usar usinge try/catch...
Peter Ritchie
1
@PeterRitchie Você pode ter um método funcionalmente assíncrono, que não use a awaitpalavra - chave e não a asyncpalavra - chave, mas não faz sentido usar a asyncpalavra - chave sem usar também awaitna definição desse método.
Servy
1
@ PeterRitchie A partir da declaração: " asyncé um pouco mais do que apenas" aguardar "um resultado". A asyncpalavra-chave (você implícita que é a palavra-chave colocando-a em backticks) significa apenas aguardar o resultado. É assincronia, como os conceitos gerais de CS, que significa mais do que apenas esperar um resultado.
Servy
1
O @Servy asynccria uma máquina de estado que gerencia qualquer espera dentro do método assíncrono. Se não houver awaits no método, ele ainda criará essa máquina de estado - mas o método não é assíncrono. E se o asyncmétodo retornar void, não há nada a aguardar. Então, é mais do que apenas esperar um resultado.
Peter Ritchie
1
@ PeterRitchie Desde que o método retorne uma tarefa, você poderá aguardá-lo. Não há necessidade da máquina de estado ou da asyncpalavra - chave. Tudo o que você precisa fazer (e tudo o que realmente acontece no final com uma máquina de estado nesse caso especial) é que o método seja executado de forma síncrona e depois envolvido em uma tarefa concluída. Suponho que tecnicamente você não apenas remova a máquina de estado; você remove a máquina de estado e depois liga Task.FromResult. Presumi que você (e também os escritores do compilador) poderia adicionar o adendo por conta própria.
Servy
0

Em tecnologias com loops de mensagens (não tenho certeza se o ASP é um deles), você pode bloquear o loop e processar as mensagens até que a tarefa termine e usar o ContinueWith para desbloquear o código:

public void WaitForTask(Task task)
{
    DispatcherFrame frame = new DispatcherFrame();
    task.ContinueWith(t => frame.Continue = false));
    Dispatcher.PushFrame(frame);
}

Essa abordagem é semelhante ao bloqueio no ShowDialog e ainda mantém a interface do usuário responsiva.

subir
fonte
0

Estou atrasado para a festa aqui, mas há uma biblioteca incrível que estou usando e que não vi mencionada nas outras respostas

https://github.com/brminnick/AsyncAwaitBestPractices

Se você precisar "Disparar e esquecer", chame o método de extensão na tarefa.

Passar a ação onException para a chamada garante que você obtenha o melhor dos dois mundos - sem necessidade de aguardar a execução e diminuir a velocidade de seus usuários, mantendo a capacidade de lidar com a exceção de maneira elegante.

No seu exemplo, você usaria assim:

   public string GetStringData()
    {
        MyAsyncMethod().SafeFireAndForget(onException: (exception) =>
                    {
                      //DO STUFF WITH THE EXCEPTION                    
                    }); 
        return "hello world";
    }

Também fornece AsyncCommands aguardando a implementação do ICommand, o que é ótimo para minha solução MVVM Xamarin

Adam Diament
fonte
-1

A solução é iniciar o HttpClient em outra tarefa de execução sem o contexto de sincronização:

var submit = httpClient.PostAsync(uri, new StringContent(body, Encoding.UTF8,"application/json"));
var t = Task.Run(() => submit.ConfigureAwait(false));
await t.ConfigureAwait(false);
Ernesto Garcia
fonte
-1

Se você realmente quer fazer isso. Apenas para abordar "Chamar um método assíncrono em C # sem esperar", você pode executar o método assíncrono dentro de a Task.Run. Essa abordagem aguardará até o MyAsyncMethodtérmino.

public string GetStringData()
{
    Task.Run(()=> MyAsyncMethod()).Result;
    return "hello world";
}

awaitdesembrulha de forma assíncrona a Resulttarefa, enquanto o uso de Result seria bloqueado até que a tarefa fosse concluída.

CharithJ
fonte
-1

Normalmente, o método assíncrono retorna a classe Task. Se você usar o Wait()método ou a Resultpropriedade e o código lança uma exceção - o tipo de exceção é agrupado AggregateException-, é necessário consultar Exception.InnerExceptionpara localizar a exceção correta.

Mas também é possível usá .GetAwaiter().GetResult()-lo - ele também espera a tarefa assíncrona, mas não quebra a exceção.

Então, aqui está um pequeno exemplo:

public async Task MyMethodAsync()
{
}

public string GetStringData()
{
    MyMethodAsync().GetAwaiter().GetResult();
    return "test";
}

Você também pode querer retornar algum parâmetro da função assíncrona - que pode ser conseguida fornecendo extra Action<return type>na função assíncrona, por exemplo:

public string GetStringData()
{
    return MyMethodWithReturnParameterAsync().GetAwaiter().GetResult();
}

public async Task<String> MyMethodWithReturnParameterAsync()
{
    return "test";
}

Observe que os métodos assíncronos geralmente têm ASyncnomes de sufixos, apenas para evitar colisões entre funções de sincronização com o mesmo nome. (Por exemplo FileStream.ReadAsync) - Atualizei os nomes das funções para seguir esta recomendação.

TarmoPikaro
fonte