Hasta ahora utilicé una tarea TPL LongRunning para el trabajo en segundo plano cíclico vinculado a la CPU en lugar del temporizador de subprocesamiento, porque:
- la tarea TPL admite la cancelación
- el temporizador de subprocesos podría iniciar otro subproceso mientras el programa se cierra, lo que causa posibles problemas con los recursos desechados
- posibilidad de desbordamiento: el temporizador de enhebrado podría iniciar otro hilo mientras el anterior todavía se está procesando debido a un trabajo largo inesperado (lo sé, se puede evitar deteniendo y reiniciando el temporizador)
Sin embargo, la solución TPL siempre reclama un hilo dedicado que no es necesario mientras se espera la siguiente acción (que es la mayor parte del tiempo). Me gustaría usar la solución propuesta de Jeff para realizar un trabajo cíclico vinculado a la CPU en segundo plano porque solo necesita un hilo de subprocesos cuando hay trabajo por hacer que es mejor para la escalabilidad (especialmente cuando el período de intervalo es grande).
Para lograrlo, sugeriría 4 adaptaciones:
- Agregue
ConfigureAwait(false)
para Task.Delay()
ejecutar la doWork
acción en un subproceso del grupo de subprocesos; de lo contrario doWork
, se realizará en el subproceso de llamada que no es la idea de paralelismo
- Siga el patrón de cancelación lanzando una TaskCanceledException (¿todavía es necesario?)
- Reenvíe el CancellationToken para
doWork
habilitarlo para cancelar la tarea
- Agregue un parámetro de tipo objeto para proporcionar información sobre el estado de la tarea (como una tarea TPL)
Acerca del punto 2, no estoy seguro, ¿async await aún requiere TaskCanceledExecption o es solo una mejor práctica?
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);
}
Por favor, dé sus comentarios a la solución propuesta ...
Actualización 2016-8-30
La solución anterior no llama inmediatamente, doWork()
sino que comienza con await Task.Delay().ConfigureAwait(false)
para lograr el cambio de hilo para doWork()
. La siguiente solución resuelve este problema al encapsular la primera doWork()
llamada en a Task.Run()
y aguardarla.
A continuación se muestra el reemplazo async \ await mejorado Threading.Timer
que realiza un trabajo cíclico cancelable y es escalable (en comparación con la solución TPL) porque no ocupa ningún hilo mientras espera la siguiente acción.
Tenga en cuenta que, al contrario que con el temporizador, el tiempo de espera ( period
) es constante y no el tiempo de ciclo; el tiempo del ciclo es la suma del tiempo de espera y la duración del doWork()
cual puede 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);
}