Quero aguardar que uma tarefa <T> seja concluída com algumas regras especiais: se ela não for concluída após X milissegundos, desejo exibir uma mensagem para o usuário. E se não for concluído após milissegundos Y, desejo solicitar automaticamente o cancelamento .
Eu posso usar Task.ContinueWith para aguardar assincronamente a tarefa (ou seja, agendar uma ação a ser executada quando a tarefa for concluída), mas isso não permite especificar um tempo limite. Posso usar o Task.Wait para aguardar de forma síncrona que a tarefa seja concluída com um tempo limite, mas isso bloqueia meu encadeamento. Como posso aguardar assincronamente a conclusão da tarefa com um tempo limite?
CancellationTokenSource
. Estão disponíveis duas sobrecargas para o construtor, uma tendo um atraso de milissegundos inteiro e outra com um atraso de TimeSpan.Respostas:
Que tal agora:
E aqui está uma excelente postagem no blog "Criando um método Task.TimeoutAfter" (da equipe da MS Parallel Library) com mais informações sobre esse tipo de coisa .
Além disso : a pedido de um comentário na minha resposta, aqui está uma solução expandida que inclui tratamento de cancelamento. Observe que passar o cancelamento para a tarefa e o cronômetro significa que há várias maneiras de o cancelamento ocorrer no seu código, e você deve testar e ter certeza de que lida com todos eles corretamente. Não deixe ao acaso várias combinações e espere que seu computador faça a coisa certa em tempo de execução.
fonte
Task.Delay
tarefa é apoiada por um cronômetro do sistema que continuará sendo rastreado até o tempo limite expirar, independentemente de quanto tempoSomeOperationAsync
leva. Portanto, se esse snippet de código geral executar muito em um loop apertado, você estará consumindo recursos do sistema para temporizadores até o tempo limite. A maneira de corrigir isso seria ter umaCancellationToken
passagemTask.Delay(timeout, cancellationToken)
que você cancela aoSomeOperationAsync
concluir para liberar o recurso de timer.Task
, qualquer exceção armazenada pela tarefa é mostrada novamente nesse ponto. Isso permite que você pegueOperationCanceledException
(se cancelado) ou qualquer outra exceção (se houver falha).Task.Wait(timeout)
bloquearia síncrona em vez de aguardar assincronamente.Aqui está uma versão do método de extensão que incorpora o cancelamento do tempo limite quando a tarefa original é concluída, conforme sugerido por Andrew Arnott em um comentário à sua resposta .
fonte
using
blocotask.Result
quando executado duas vezes.task
) ainda continuará em execução no caso de um tempo limite?TimeoutException
possui uma mensagem padrão adequada. Substituindo-o por "A operação atingiu o tempo limite". não agrega valor e, na verdade, causa alguma confusão, implicando que há uma razão para substituí-lo.Você pode usar
Task.WaitAny
para aguardar a primeira de várias tarefas.Você pode criar duas tarefas adicionais (que são concluídas após os tempos limite especificados) e depois usar
WaitAny
para aguardar o que for concluído primeiro. Se a tarefa que foi concluída primeiro for a sua "obra", você estará pronto. Se a tarefa que foi concluída primeiro for uma tarefa de tempo limite, você poderá reagir ao tempo limite (por exemplo, solicitar cancelamento).fonte
below
.... qual é? com base no pedido de SO?Que tal algo assim?
Você pode usar a opção Task.Wait sem bloquear o thread principal usando outra tarefa.
fonte
Aqui está um exemplo completo, com base na resposta mais votada, que é:
A principal vantagem da implementação nesta resposta é que os genéricos foram adicionados, para que a função (ou tarefa) possa retornar um valor. Isso significa que qualquer função existente pode ser agrupada em uma função de tempo limite, por exemplo:
Antes:
Depois de:
Este código requer o .NET 4.5.
Ressalvas
Tendo dado essa resposta, geralmente não é uma boa prática ter exceções lançadas em seu código durante a operação normal, a menos que você precise:
Somente use esse código se você absolutamente não puder alterar a função que está chamando, para que o tempo limite seja excedido após um determinado
TimeSpan
.Essa resposta é realmente aplicável apenas ao lidar com bibliotecas de bibliotecas de terceiros que você simplesmente não pode refatorar para incluir um parâmetro de tempo limite.
Como escrever código robusto
Se você deseja escrever um código robusto, a regra geral é esta:
Se você não observar essa regra, seu código acabará atingindo uma operação que falhará por algum motivo, bloqueará indefinidamente e seu aplicativo será interrompido permanentemente.
Se houvesse um tempo limite razoável após algum tempo, seu aplicativo seria interrompido por um período extremo de tempo (por exemplo, 30 segundos). Ele exibia um erro e continuava em seu caminho alegre ou tentava novamente.
fonte
Usando a excelente biblioteca AsyncEx de Stephen Cleary , você pode:
TaskCanceledException
será lançado no caso de um tempo limite.fonte
Esta é uma versão ligeiramente aprimorada das respostas anteriores.
CancellationToken
a tarefa original e, quando ocorre o tempo limite, você obtém oTimeoutException
invésOperationCanceledException
.Uso
InnerCallAsync
pode levar muito tempo para concluir.CallAsync
envolve com um tempo limite.fonte
timeoutCancellation
paradelayTask
. Atualmente, se você acionar o cancelamento,CancelAfterAsync
pode lançar emTimeoutException
vez deTaskCanceledException
, a causadelayTask
pode terminar primeiro.WhenAny
forem canceladas pelo mesmo token,WhenAny
retornará a primeira tarefa. Essa suposição estava errada. Eu editei a resposta. Obrigado!Use um timer para lidar com a mensagem e o cancelamento automático. Quando a tarefa terminar, chame Dispose nos timers para que eles nunca disparem. Aqui está um exemplo; altere taskDelay para 500, 1500 ou 2500 para ver os diferentes casos:
Além disso, o CTP assíncrono fornece um método TaskEx.Delay que agrupará os cronômetros em tarefas para você. Isso pode lhe dar mais controle para executar ações como definir o TaskScheduler para a continuação quando o Timer for disparado.
fonte
task.Wait()
.Outra maneira de resolver esse problema é usar extensões reativas:
Teste acima usando o código abaixo no seu teste de unidade, ele funciona para mim
Você pode precisar do seguinte espaço para nome:
fonte
Uma versão genérica da resposta de @ Kevan acima, usando Extensões Reativas.
Com o Agendador opcional:
BTW: Quando ocorrer um tempo limite, uma exceção de tempo limite será lançada
fonte
Se você usar um BlockingCollection para agendar a tarefa, o produtor poderá executar a tarefa potencialmente demorada e o consumidor poderá usar o método TryTake, que possui um token de tempo limite e cancelamento incorporado.
fonte
Eu senti a
Task.Delay()
tarefa eCancellationTokenSource
nas outras respostas um pouco demais para o meu caso de uso em um loop restrito de rede.E embora o método Crafting a Task.TimeoutAfter de Joe Hoag nos blogs do MSDN tenha sido inspirador, eu estava um pouco cansado de usar o
TimeoutException
controle de fluxo pelo mesmo motivo acima, porque os tempos limite são esperados com mais frequência do que não.Então eu fui com isso, que também lida com as otimizações mencionadas no blog:
Um exemplo de caso de uso é o seguinte:
fonte
Algumas variantes da resposta de Andrew Arnott:
Se você deseja aguardar uma tarefa existente e descobrir se ela foi concluída ou excedeu o tempo limite, mas não deseja cancelá-la se ocorrer o tempo limite:
Se você deseja iniciar uma tarefa de trabalho e cancelar o trabalho se ocorrer o tempo limite:
Se você já tiver uma tarefa criada que deseja cancelar se ocorrer um tempo limite:
Outro comentário: essas versões cancelarão o cronômetro se o tempo limite não ocorrer, portanto, várias chamadas não farão com que os cronômetros se acumulem.
sjb
fonte
Estou recombuindo as idéias de algumas outras respostas aqui e essa resposta em outro thread em um método de extensão no estilo Try. Isso tem um benefício se você deseja um método de extensão, evitando uma exceção após o tempo limite.
fonte
Definitivamente não faça isso, mas é uma opção se ... Não consigo pensar em um motivo válido.
fonte