O operador de espera não está esperando como eu esperava
Estou trabalhando em uma aulaDelayedExecutor
que atrasará a execução de umAction
passou para o seuDelayExecute
método por um certo tempotimeout
(veja o código abaixo) usando as instruções assíncronas e aguardar. Eu também quero poder abortar a execução dentro dotimeout
intervalo, se necessário. Eu escrevi um pequeno teste para testar seu comportamento assim:
Código paraMétodo de teste:
[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);
}
}
A saída que eu esperava deste teste foi que ele me mostraria:
sw.ElapsedMilliseconds fora de DelayExecute 1: ...
Ação Ran! "
sw.ElapsedMilliseconds dentro de DelayExecute: ...
sw.ElapsedMilliseconds fora do DelayExecute 2:
No entanto, eu entendo isso e não entendo o porquê:
sw.ElapsedMilliseconds fora de DelayExecute 1: 0
sw.ElapsedMilliseconds fora do atrasoExecute 2: 30
Ran Action!
sw.ElapsedMilliseconds dentro de DelayExecute: 1015
Nistopublicação no blog Eu li:
Eu gosto de pensar em "aguardar" como uma "espera assíncrona". Ou seja, o método assíncrono faz uma pausa até que o esperado seja concluído (o que aguarda), mas o encadeamento real não é bloqueado (portanto, é assíncrono).
Isso parece estar alinhado com a minha expectativa, então o que está acontecendo aqui e onde está o meu erro?
Código paraDelayedExecutor:
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);
});
}
}
Atualizar:
Conforme sugerido, modifiquei esta linha dentro do meuDelayExecute
return currentTask = Task.Factory.StartNew(async () =>
para dentro
return currentTask = await Task.Factory.StartNew(async () =>
Para que isso funcionasse, eu precisava alterar a assinatura para este
public async Task<Task> DelayExecute(Action func)
para que minha nova definição seja 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);
});
}
No entanto, agora eu tenho o mesmo comportamento de antes. Existe alguma maneira de conseguir o que estou tentando fazer usando meu design. Ou é fundamentalmente falho?
A propósito, eu também tentei colocarawait await delayTask;
dentro do meu testeDelayExecuteTest
, mas isso me dá o erro
Não é possível aguardar 'nulo'
Este é o método de teste atualizadoDelayExecuteTest
, qualnão 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);
}