Fino ad ora ho utilizzato un'attività TPL LongRunning per il lavoro in background ciclico associato alla CPU invece del timer di threading, perché:
- l'attività TPL supporta l'annullamento
- il timer di threading potrebbe avviare un altro thread mentre il programma si sta chiudendo causando possibili problemi con le risorse eliminate
- possibilità di sovraccarico: il timer di threading potrebbe avviare un altro thread mentre il precedente è ancora in fase di elaborazione a causa di un lungo lavoro inaspettato (lo so, può essere prevenuto arrestando e riavviando il timer)
Tuttavia, la soluzione TPL richiede sempre un thread dedicato che non è necessario in attesa dell'azione successiva (che è la maggior parte delle volte). Vorrei utilizzare la soluzione proposta di Jeff per eseguire il lavoro ciclico legato alla CPU in background perché ha bisogno solo di un thread di threadpool quando c'è del lavoro da fare che è meglio per la scalabilità (specialmente quando il periodo di intervallo è grande).
Per ottenere ciò, suggerirei 4 adattamenti:
- Aggiungi
ConfigureAwait(false)
a Task.Delay()
per eseguire l' doWork
azione su un thread del pool di thread, altrimenti doWork
verrà eseguita sul thread chiamante che non è l'idea di parallelismo
- Attenersi al modello di cancellazione lanciando un'eccezione TaskCanceledException (ancora necessaria?)
- Inoltrare il CancellationToken a
doWork
per abilitarlo all'annullamento dell'attività
- Aggiungi un parametro di tipo oggetto per fornire informazioni sullo stato dell'attività (come un'attività TPL)
Riguardo al punto 2, non sono sicuro, l'asincronizzazione attende ancora richiede TaskCanceledExecption o è solo una best practice?
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);
}
Si prega di commentare la soluzione proposta ...
Aggiornamento 2016-8-30
La soluzione precedente non chiama immediatamente doWork()
ma inizia con await Task.Delay().ConfigureAwait(false)
per ottenere il thread switch per doWork()
. La soluzione seguente risolve questo problema avvolgendo la prima doWork()
chiamata in una Task.Run()
e aspettandola.
Di seguito è riportato il sostituto async \ await migliorato Threading.Timer
che esegue un lavoro ciclico cancellabile ed è scalabile (rispetto alla soluzione TPL) perché non occupa alcun thread in attesa dell'azione successiva.
Notare che al contrario del Timer, il tempo di attesa ( period
) è costante e non il tempo di ciclo; il tempo di ciclo è la somma del tempo di attesa e la cui durata doWork()
può variare.
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);
}