Portanto, meu aplicativo precisa executar uma ação quase continuamente (com uma pausa de 10 segundos ou mais entre cada execução) enquanto o aplicativo estiver em execução ou um cancelamento for solicitado. O trabalho que ela precisa fazer pode levar até 30 segundos.
É melhor usar um System.Timers.Timer e usar o AutoReset para garantir que ele não execute a ação antes que o "tique" anterior seja concluído.
Ou devo usar uma tarefa geral no modo LongRunning com um token de cancelamento e ter um loop while infinito regular dentro dele chamando a ação que faz o trabalho com um Thread.Sleep de 10 segundos entre as chamadas? Quanto ao modelo async / await, não tenho certeza se seria apropriado aqui, pois não tenho nenhum valor de retorno do trabalho.
CancellationTokenSource wtoken;
Task task;
void StopWork()
{
wtoken.Cancel();
try
{
task.Wait();
} catch(AggregateException) { }
}
void StartWork()
{
wtoken = new CancellationTokenSource();
task = Task.Factory.StartNew(() =>
{
while (true)
{
wtoken.Token.ThrowIfCancellationRequested();
DoWork();
Thread.Sleep(10000);
}
}, wtoken, TaskCreationOptions.LongRunning);
}
void DoWork()
{
// Some work that takes up to 30 seconds but isn't returning anything.
}
ou apenas usar um temporizador simples enquanto usa sua propriedade AutoReset e chamar .Stop () para cancelá-lo?
Respostas:
Eu usaria o TPL Dataflow para isso (já que você está usando o .NET 4.5 e ele usa
Task
internamente). Você pode criar facilmente umActionBlock<TInput>
que posta itens para si mesmo depois de processado sua ação e aguardado um período de tempo apropriado.Primeiro, crie uma fábrica que criará sua tarefa sem fim:
Eu escolhi o
ActionBlock<TInput>
para ter umaDateTimeOffset
estrutura ; você tem que passar um parâmetro de tipo, e também pode passar algum estado útil (você pode alterar a natureza do estado, se quiser).Além disso, observe que,
ActionBlock<TInput>
por padrão, processa apenas um item por vez, então você tem a garantia de que apenas uma ação será processada (ou seja, você não terá que lidar com a reentrada quando ele chamar oPost
método de extensão de volta em si).Também passei a
CancellationToken
estrutura para o construtor deActionBlock<TInput>
e para a chamada doTask.Delay
método ; se o processo for cancelado, o cancelamento ocorrerá na primeira oportunidade possível.A partir daí, é uma refatoração fácil de seu código para armazenar a
ITargetBlock<DateTimeoffset>
interface implementadaActionBlock<TInput>
(esta é a abstração de nível superior que representa os blocos que são consumidores e você deseja ser capaz de acionar o consumo por meio de uma chamada aoPost
método de extensão):Seu
StartWork
método:E então seu
StopWork
método:Por que você deseja usar o TPL Dataflow aqui? Alguns motivos:
Separação de preocupações
O
CreateNeverEndingTask
método agora é uma fábrica que cria seu "serviço", por assim dizer. Você controla quando ele começa e para, e é totalmente independente. Você não precisa entrelaçar o controle de estado do cronômetro com outros aspectos do seu código. Você simplesmente cria o bloco, inicia-o e interrompe-o quando terminar.Uso mais eficiente de threads / tarefas / recursos
O agendador padrão para os blocos no fluxo de dados TPL é o mesmo para a
Task
, que é o pool de threads. Ao usar oActionBlock<TInput>
para processar sua ação, bem como uma chamada paraTask.Delay
, você está cedendo o controle do encadeamento que estava usando quando, na verdade, não estava fazendo nada. Concedido, isso realmente leva a alguma sobrecarga quando você cria o novoTask
que processará a continuação, mas isso deve ser pequeno, considerando que você não está processando isso em um loop apertado (você está esperando dez segundos entre as invocações).Se a
DoWork
função realmente pode ser tornada aguardável (ou seja, em que retorna aTask
), então você pode (possivelmente) otimizar isso ainda mais ajustando o método de fábrica acima para obter um emFunc<DateTimeOffset, CancellationToken, Task>
vez de umAction<DateTimeOffset>
, como:Obviamente, seria uma boa prática incluir
CancellationToken
o método em seu método (se ele aceitar), o que é feito aqui.Isso significa que você teria um
DoWorkAsync
método com a seguinte assinatura:Você teria que mudar (apenas ligeiramente, e você não está eliminando a separação de preocupações aqui) o
StartWork
método para contabilizar a nova assinatura passada para oCreateNeverEndingTask
método, assim:fonte
Acho que a nova interface baseada em tarefas é muito simples para fazer coisas como esta - ainda mais fácil do que usar a classe Timer.
Existem alguns pequenos ajustes que você pode fazer em seu exemplo. Ao invés de:
Você consegue fazer isso:
Desta forma, o cancelamento acontecerá instantaneamente se dentro do
Task.Delay
, ao invés de ter que esperar peloThread.Sleep
término do.Além disso, usando
Task.Delay
maisThread.Sleep
significa que você não está amarrando uma linha sem fazer nada durante o sono.Se puder, você também pode
DoWork()
aceitar um token de cancelamento, e o cancelamento será muito mais ágil.fonte
Task.Run
usa o pool de threads, então seu exemplo usando emTask.Run
vez deTask.Factory.StartNew
comTaskCreationOptions.LongRunning
não faz exatamente a mesma coisa - se eu precisasse da tarefa para usar aLongRunning
opção, não seria capaz de usarTask.Run
como você mostrou ou estou faltando alguma coisa?LongRunning
é incompatível com o objetivo de não amarrar os fios. Se você quiser garantir a execução em seu próprio encadeamento, poderá usá-lo, mas aqui você iniciará um encadeamento que está inativo a maior parte do tempo. Qual é o caso de uso?LongRunning
usando aTask.Run
sintaxe. Pela documentação, parece umaTask.Run
sintaxe mais limpa, contanto que você esteja satisfeito com as configurações padrão que ela usa. Não parece haver uma sobrecarga que exige umTaskCreationOptions
argumento.Aqui está o que eu descobri:
NeverEndingTask
e sobrescrever oExecutionCore
método com o trabalho que você deseja fazer.ExecutionLoopDelayMs
permite que você ajuste o tempo entre os loops, por exemplo, se você quiser usar um algoritmo de backoff.Start/Stop
fornecer uma interface síncrona para iniciar / parar a tarefa.LongRunning
significa que você receberá um tópico dedicado porNeverEndingTask
.ActionBlock
solução baseada acima.:
fonte