Por lo tanto, mi aplicación debe realizar una acción casi continuamente (con una pausa de 10 segundos aproximadamente entre cada ejecución) mientras la aplicación se esté ejecutando o se solicite una cancelación. El trabajo que necesita hacer tiene la posibilidad de llevar hasta 30 segundos.
¿Es mejor usar un System.Timers.Timer y usar AutoReset para asegurarse de que no realice la acción antes de que se complete el "tick" anterior?
¿O debería usar una Tarea general en el modo LongRunning con un token de cancelación y tener un bucle while regular infinito dentro de él llamando a la acción que hace el trabajo con un Thread.Sleep de 10 segundos entre llamadas? En cuanto al modelo async / await, no estoy seguro de que sea apropiado aquí, ya que no tengo ningún valor de retorno del trabajo.
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.
}
¿O simplemente usa un temporizador simple mientras usa su propiedad AutoReset y llama a .Stop () para cancelarlo?
Respuestas:
Que haría uso de TPL de flujo de datos para esto (ya que usted está utilizando .NET 4.5 y utiliza
Task
internamente). Puede crear fácilmente un elementoActionBlock<TInput>
que se publicará a sí mismo después de que se procesó su acción y se esperó una cantidad de tiempo adecuada.Primero, crea una fábrica que creará tu tarea sin fin:
Elegí el
ActionBlock<TInput>
para tomar unaDateTimeOffset
estructura ; tiene que pasar un parámetro de tipo, y también podría pasar algún estado útil (puede cambiar la naturaleza del estado, si lo desea).Además, tenga en cuenta que,
ActionBlock<TInput>
de forma predeterminada, procesa solo un elemento a la vez, por lo que tiene la garantía de que solo se procesará una acción (lo que significa que no tendrá que lidiar con la reentrada cuando vuelva a llamar alPost
método de extensión ).También pasé la
CancellationToken
estructura tanto al constructor delActionBlock<TInput>
como a la llamada alTask.Delay
método ; si se cancela el proceso, la cancelación se realizará en la primera oportunidad posible.A partir de ahí, es una fácil refactorización de su código para almacenar la
ITargetBlock<DateTimeoffset>
interfaz implementada porActionBlock<TInput>
(esta es la abstracción de nivel superior que representa bloques que son consumidores, y desea poder desencadenar el consumo a través de una llamada alPost
método de extensión):Tu
StartWork
método:Y luego tu
StopWork
método:¿Por qué querría utilizar TPL Dataflow aquí? Algunas razones:
Separación de intereses
El
CreateNeverEndingTask
método es ahora una fábrica que crea su "servicio" por así decirlo. Tú controlas cuándo se inicia y se detiene, y es completamente autónomo. No tiene que entrelazar el control de estado del temporizador con otros aspectos de su código. Simplemente crea el bloque, inícielo y deténgalo cuando haya terminado.Uso más eficiente de hilos / tareas / recursos
El planificador predeterminado para los bloques en el flujo de datos de TPL es el mismo para a
Task
, que es el grupo de subprocesos. Al usar elActionBlock<TInput>
para procesar su acción, así como una llamada aTask.Delay
, cede el control del hilo que estaba usando cuando en realidad no está haciendo nada. Por supuesto, esto en realidad genera algunos gastos generales cuando genera el nuevoTask
que procesará la continuación, pero eso debería ser pequeño, considerando que no está procesando esto en un ciclo cerrado (está esperando diez segundos entre invocaciones).Si la
DoWork
función realmente se puede convertir en esperable (es decir, porque devuelve aTask
), entonces puede (posiblemente) optimizar esto aún más ajustando el método de fábrica anterior para tomar a enFunc<DateTimeOffset, CancellationToken, Task>
lugar de anAction<DateTimeOffset>
, así:Por supuesto, sería una buena práctica
CancellationToken
pasar por alto su método (si acepta uno), que se hace aquí.Eso significa que entonces tendría un
DoWorkAsync
método con la siguiente firma:Tendría que cambiar (solo un poco, y no está desangrando la separación de preocupaciones aquí) el
StartWork
método para dar cuenta de la nueva firma pasada alCreateNeverEndingTask
método, así:fuente
Encuentro que la nueva interfaz basada en tareas es muy simple para hacer cosas como esta, incluso más fácil que usar la clase Timer.
Hay algunos pequeños ajustes que puede hacer en su ejemplo. En vez de:
Puedes hacerlo:
De esta manera la cancelación ocurrirá instantáneamente si está dentro del
Task.Delay
, en lugar de tener que esperar aThread.Sleep
que termine.Además, usar
Task.Delay
overThread.Sleep
significa que no está atando un hilo sin hacer nada durante la duración del sueño.Si puede, también puede
DoWork()
aceptar un token de cancelación, y la cancelación será mucho más receptiva.fuente
Task.Run
usa el grupo de subprocesos, por lo que su ejemplo usando enTask.Run
lugar deTask.Factory.StartNew
conTaskCreationOptions.LongRunning
no hace exactamente lo mismo - si necesitara la tarea para usar laLongRunning
opción, ¿no podría usarlaTask.Run
como ha mostrado o me falta algo?LongRunning
es algo incompatible con el objetivo de no atar hilos. Si desea garantizar que se ejecute en su propio hilo, puede usarlo, pero aquí va a iniciar un hilo que está inactivo la mayor parte del tiempo. ¿Cuál es el caso de uso?LongRunning
use laTask.Run
sintaxis. Según la documentación, parece queTask.Run
tiene una sintaxis más limpia, siempre que esté satisfecho con la configuración predeterminada que utiliza. No parece haber una sobrecarga que requiera unaTaskCreationOptions
discusión.Esto es lo que se me ocurrió:
NeverEndingTask
y anule elExecutionCore
método con el trabajo que desea hacer.ExecutionLoopDelayMs
permite ajustar el tiempo entre bucles, por ejemplo, si desea utilizar un algoritmo de retroceso.Start/Stop
proporcionar una interfaz síncrona para iniciar / detener la tarea.LongRunning
significa que obtendrá un hilo dedicado porNeverEndingTask
.ActionBlock
solución basada arriba.:
fuente