c# - ¿Cuál es la forma correcta de combinar tareas de larga ejecución con un patrón asíncrono/espera?
xamarin.ios task-parallel-library (1)
Tengo una clase de temporizador de "Alta precisión" que necesito para poder iniciar, detener y pausar / reanudar. Para hacer esto, estoy uniendo un par de ejemplos diferentes que encontré en Internet, pero no estoy seguro de si estoy usando Tareas con asnyc / await correctamente.
Aquí está mi código relevante:
//based on http://haukcode.wordpress.com/2013/01/29/high-precision-timer-in-netc/
public class HighPrecisionTimer : IDisposable
{
Task _task;
CancellationTokenSource _cancelSource;
//based on http://blogs.msdn.com/b/pfxteam/archive/2013/01/13/cooperatively-pausing-async-methods.aspx
PauseTokenSource _pauseSource;
Stopwatch _watch;
Stopwatch Watch { get { return _watch ?? (_watch = Stopwatch.StartNew()); } }
public bool IsPaused
{
get { return _pauseSource != null && _pauseSource.IsPaused; }
private set
{
if (value)
{
_pauseSource = new PauseTokenSource();
}
else
{
_pauseSource.IsPaused = false;
}
}
}
public bool IsRunning { get { return !IsPaused && _task != null && _task.Status == TaskStatus.Running; } }
public void Start()
{
if (IsPaused)
{
IsPaused = false;
}
else if (!IsRunning)
{
_cancelSource = new CancellationTokenSource();
_task = new Task(ExecuteAsync, _cancelSource.Token, TaskCreationOptions.LongRunning);
_task.Start();
}
}
public void Stop()
{
if (_cancelSource != null)
{
_cancelSource.Cancel();
}
}
public void Pause()
{
if (!IsPaused)
{
if (_watch != null)
{
_watch.Stop();
}
}
IsPaused = !IsPaused;
}
async void ExecuteAsync()
{
while (!_cancelSource.IsCancellationRequested)
{
if (_pauseSource != null && _pauseSource.IsPaused)
{
await _pauseSource.Token.WaitWhilePausedAsync();
}
// DO CUSTOM TIMER STUFF...
}
if (_watch != null)
{
_watch.Stop();
_watch = null;
}
_cancelSource = null;
_pauseSource = null;
}
public void Dispose()
{
if (IsRunning)
{
_cancelSource.Cancel();
}
}
}
¿Alguien puede, por favor, echar un vistazo y proporcionarme algunos consejos sobre si estoy haciendo esto correctamente?
ACTUALIZAR
He intentado modificar mi código según los comentarios de Noseratio a continuación, pero todavía no puedo entender la sintaxis. Cada intento de pasar el método ExecuteAsync () a TaskFactory.StartNew o Task.Run , genera un error de compilación como el siguiente:
"La llamada es ambigua entre los siguientes métodos o propiedades: TaskFactory.StartNew (Action, CancelToken ...) y TaskFactory.StartNew <Task> (Func <Task>, CancelToken ...)".
Por último, ¿hay una manera de especificar el LongRunning TaskCreationOption sin tener que proporcionar un TaskScheduler?
async **Task** ExecuteAsync()
{
while (!_cancelSource.IsCancellationRequested)
{
if (_pauseSource != null && _pauseSource.IsPaused)
{
await _pauseSource.Token.WaitWhilePausedAsync();
}
//...
}
}
public void Start()
{
//_task = Task.Factory.StartNew(ExecuteAsync, _cancelSource.Token, TaskCreationOptions.LongRunning, null);
//_task = Task.Factory.StartNew(ExecuteAsync, _cancelSource.Token);
//_task = Task.Run(ExecuteAsync, _cancelSource.Token);
}
ACTUALIZACIÓN 2
Creo que he reducido esto, pero todavía no estoy seguro de la sintaxis correcta. ¿Sería esta la manera correcta de crear la tarea de manera que el código de consumidor / llamante continúe, con la tarea girando y comenzando en un nuevo hilo asíncrono?
_task = Task.Run(async () => await ExecuteAsync, _cancelSource.Token);
//**OR**
_task = Task.Factory.StartNew(async () => await ExecuteAsync, _cancelSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
Aquí hay algunos puntos:
async void
métodos deasync void
solo son buenos para los manejadores de eventos asíncronos ( más información ). Suasync void ExecuteAsync()
devuelve instantáneamente (tan pronto como el flujo de código alcanza aawait _pauseSource
dentro de él). Esencialmente, su_task
está en el estado completado después de eso, mientras que el resto deExecuteAsync
se ejecutará sin ser observado (porque esvoid
). Incluso es posible que no continúe ejecutándose en absoluto, dependiendo de cuándo finalice su subproceso principal (y, por lo tanto, el proceso).Dado esto, debe hacerlo
async Task ExecuteAsync()
, y usarTask.Run
oTask.Factory.StartNew
lugar de lanew Task
para iniciarla. Debido a que desea que el método de acción de su tarea seaasync
, estaría tratando con tareas anidadas aquí, es decir,Task<Task>
, queTask.Run
desenvolvería automáticamente para usted. Más información se puede encontrar here y here .PauseTokenSource
adopta el siguiente enfoque (por diseño, AFAIU): el lado del código del consumidor (el que llamaPause
) en realidad solo solicita una pausa, pero no se sincroniza. Continuará ejecutándose después dePause
, incluso aunque el lado del productor aún no haya alcanzado el estado de espera, es decir,await _pauseSource.Token.WaitWhilePausedAsync()
. Esto puede estar bien para la lógica de tu aplicación, pero debes tenerlo en cuenta. Más información here .
[ACTUALIZACIÓN] A continuación se muestra la sintaxis correcta para usar Factory.StartNew
. Nota Task<Task>
y task.Unwrap
. También tenga en cuenta _task.Wait()
en Stop
, está allí para asegurarse de que la tarea se haya completado cuando se Stop
(de manera similar a Thread.Join
). Además, TaskScheduler.Default
se utiliza para indicar a Factory.StartNew
que use el programador de agrupaciones de hebras. Esto es importante si crea su objeto HighPrecisionTimer
desde otra tarea, que a su vez se creó en un hilo con un contexto de sincronización no predeterminado, por ejemplo, un hilo UI (más información here y here ).
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication
{
public class HighPrecisionTimer
{
Task _task;
CancellationTokenSource _cancelSource;
public void Start()
{
_cancelSource = new CancellationTokenSource();
Task<Task> task = Task.Factory.StartNew(
function: ExecuteAsync,
cancellationToken: _cancelSource.Token,
creationOptions: TaskCreationOptions.LongRunning,
scheduler: TaskScheduler.Default);
_task = task.Unwrap();
}
public void Stop()
{
_cancelSource.Cancel(); // request the cancellation
_task.Wait(); // wait for the task to complete
}
async Task ExecuteAsync()
{
Console.WriteLine("Enter ExecuteAsync");
while (!_cancelSource.IsCancellationRequested)
{
await Task.Delay(42); // for testing
// DO CUSTOM TIMER STUFF...
}
Console.WriteLine("Exit ExecuteAsync");
}
}
class Program
{
public static void Main()
{
var highPrecisionTimer = new HighPrecisionTimer();
Console.WriteLine("Start timer");
highPrecisionTimer.Start();
Thread.Sleep(2000);
Console.WriteLine("Stop timer");
highPrecisionTimer.Stop();
Console.WriteLine("Press Enter to exit...");
Console.ReadLine();
}
}
}