Até agora eu usei uma tarefa TPL LongRunning para trabalho em segundo plano cíclico de CPU em vez do temporizador de threading, porque:
- a tarefa TPL suporta cancelamento
- o temporizador de threading pode iniciar outro thread enquanto o programa está sendo encerrado, causando possíveis problemas com recursos descartados
- chance de saturação: o cronômetro de threading pode iniciar outro encadeamento enquanto o anterior ainda está sendo processado devido a um longo trabalho inesperado (eu sei, pode ser evitado parando e reiniciando o cronômetro)
No entanto, a solução TPL sempre reivindica um thread dedicado que não é necessário enquanto espera pela próxima ação (que é na maioria das vezes). Eu gostaria de usar a solução proposta de Jeff para realizar trabalho cíclico vinculado à CPU em segundo plano, porque ela só precisa de um thread de pool de threads quando há trabalho a ser feito, o que é melhor para escalabilidade (especialmente quando o período de intervalo é grande).
Para conseguir isso, gostaria de sugerir 4 adaptações:
- Adicione
ConfigureAwait(false)
ao Task.Delay()
para executar a doWork
ação em um thread do pool de threads, caso contráriodoWork
será realizada na thread de chamada, que não é a ideia de paralelismo
- Atenha-se ao padrão de cancelamento lançando uma TaskCanceledException (ainda necessária?)
- Encaminhe o CancelamentoToken para
doWork
para habilitá-lo a cancelar a tarefa
- Adicione um parâmetro do tipo objeto para fornecer informações de estado da tarefa (como uma tarefa TPL)
Sobre o ponto 2, não tenho certeza, o async await ainda requer a TaskCanceledExecption ou é apenas uma prática recomendada?
public static async Task Run(Action<object, CancellationToken> doWork, object taskState, TimeSpan period, CancellationToken cancellationToken)
{
do
{
await Task.Delay(period, cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
doWork(taskState, cancellationToken);
}
while (true);
}
Dê seus comentários sobre a solução proposta ...
Atualização 2016-8-30
A solução acima não chama imediatamente, doWork()
mas começa com await Task.Delay().ConfigureAwait(false)
para alcançar a troca de thread para doWork()
. A solução abaixo supera esse problema envolvendo a primeira doWork()
chamada em a Task.Run()
e aguardando-a.
Abaixo está a substituição async \ await aprimorada para Threading.Timer
que executa trabalho cíclico cancelável e é escalonável (em comparação com a solução TPL) porque não ocupa nenhum thread enquanto espera pela próxima ação.
Note que ao contrário do Timer, o tempo de espera ( period
) é constante e não o tempo do ciclo; o tempo de ciclo é a soma do tempo de espera e cuja duração doWork()
pode variar.
public static async Task Run(Action<object, CancellationToken> doWork, object taskState, TimeSpan period, CancellationToken cancellationToken)
{
await Task.Run(() => doWork(taskState, cancellationToken), cancellationToken).ConfigureAwait(false);
do
{
await Task.Delay(period, cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
doWork(taskState, cancellationToken);
}
while (true);
}