El operador espera no está esperando como esperaba
Estoy trabajando en una claseDelayedExecutor
eso retrasará la ejecución de unAction
pasado a suDelayExecute
método por un tiempo determinadotimeout
(vea el código a continuación) usando las declaraciones async y wait. También quiero poder abortar la ejecución dentro deltimeout
intervalo si es necesario. He escrito una pequeña prueba para probar su comportamiento de esta manera:
Código paraMétodo de prueba:
[TestMethod]
public async Task DelayExecuteTest()
{
int timeout = 1000;
var delayExecutor = new DelayedExecutor(timeout);
Action func = new Action(() => Debug.WriteLine("Ran function!"));
var sw = Stopwatch.StartNew();
Debug.WriteLine("sw.ElapsedMilliseconds 1: " + sw.ElapsedMilliseconds);
Task delayTask = delayExecutor.DelayExecute(func);
await delayTask;
Debug.WriteLine("sw.ElapsedMilliseconds 2: " + sw.ElapsedMilliseconds);
Thread.Sleep(1000);
}
}
El resultado que esperaba de esta prueba fue que me mostraría:
sw.ElapsedMilliseconds fuera de DelayExecute 1: ...
Ran Action! "
sw.ElapsedMilliseconds dentro de DelayExecute: ...
sw.ElapsedMilliseconds fuera de DelayExecute 2:
Sin embargo, entiendo esto y no entiendo por qué:
sw.ElapsedMilliseconds fuera de DelayExecute 1: 0
sw.ElapsedMilliseconds fuera de DelayExecute 2: 30
Ran Action!
sw.ElapsedMilliseconds dentro de DelayExecute: 1015
En esteentrada en el blog Yo leo:
Me gusta pensar en "esperar" como una "espera asincrónica". Es decir, el método asíncrono se detiene hasta que se completa lo esperado (por lo que espera), pero el subproceso real no está bloqueado (por lo que es asíncrono).
Esto parece estar en línea con mis expectativas, entonces, ¿qué está pasando aquí y dónde está mi error?
Código paraEjecutor retrasado:
public class DelayedExecutor
{
private int timeout;
private Task currentTask;
private CancellationToken cancellationToken;
private CancellationTokenSource tokenSource;
public DelayedExecutor(int timeout)
{
this.timeout = timeout;
tokenSource = new CancellationTokenSource();
}
public void AbortCurrentTask()
{
if (currentTask != null)
{
if (!currentTask.IsCompleted)
{
tokenSource.Cancel();
}
}
}
public Task DelayExecute(Action func)
{
AbortCurrentTask();
tokenSource = new CancellationTokenSource();
cancellationToken = tokenSource.Token;
return currentTask = Task.Factory.StartNew(async () =>
{
var sw = Stopwatch.StartNew();
await Task.Delay(timeout, cancellationToken);
func();
Debug.WriteLine("sw.ElapsedMilliseconds inside DelayExecute: " + sw.ElapsedMilliseconds);
});
}
}
Actualizar:
Como sugerí, modifiqué esta línea dentro de miDelayExecute
return currentTask = Task.Factory.StartNew(async () =>
dentro
return currentTask = await Task.Factory.StartNew(async () =>
Para que esto funcione, necesitaba cambiar la firma a esto
public async Task<Task> DelayExecute(Action func)
para que mi nueva definición sea esta:
public async Task<Task> DelayExecute(Action func)
{
AbortCurrentTask();
tokenSource = new CancellationTokenSource();
cancellationToken = tokenSource.Token;
return currentTask = await Task.Factory.StartNew(async () =>
{
var sw = Stopwatch.StartNew();
await Task.Delay(timeout, cancellationToken);
func();
Debug.WriteLine("sw.ElapsedMilliseconds inside DelayExecute: " + sw.ElapsedMilliseconds);
});
}
Sin embargo, ahora tengo el mismo comportamiento que antes. ¿Hay alguna forma de lograr lo que estoy tratando de hacer con mi diseño? ¿O es fundamentalmente defectuoso?
Por cierto, también intenté ponerawait await delayTask;
dentro de mi pruebaDelayExecuteTest
, pero esto me da el error
No puedo esperar 'vacío'
Este es el método de prueba actualizadoDelayExecuteTest
, cualno compilar:
public async Task DelayExecuteTest()
{
int timeout = 1000;
var delayExecutor = new DelayedExecutor(timeout);
Action func = new Action(() => Debug.WriteLine("Ran Action!"));
var sw = Stopwatch.StartNew();
Debug.WriteLine("sw.ElapsedMilliseconds outside DelayExecute 1: " + sw.ElapsedMilliseconds);
Task delayTask = delayExecutor.DelayExecute(func);
await await delayTask;
Debug.WriteLine("sw.ElapsedMilliseconds outside DelayExecute 2: " + sw.ElapsedMilliseconds);
Thread.Sleep(1000);
}