Problemas al intentar usar async / await [duplicate]
Estoy siguiendo este hilo para hacer que algunos de mis comandos sean asíncronos mientras que otros se sincronizan, pero tengo algunos problemas.
Esto es lo que tengo hasta ahora.
Una interfaz:
public interface ICommand
{
public Task ExecuteAsync();
}
Lo cual implemento en un comando de espera que debería ser sincrónico.
public class WaitCommand : ICommand
{
public Task ExecuteAsync()
{
Console.WriteLine("Sleeping...");
Thread.Sleep(5000);
Console.WriteLine("Waking up...");
return Task.CompletedTask;
}
}
Y otro comando que funciona y debería ser asíncrono.
public class ConcreteCommand : ICommand
{
public async Task ExecuteAsync()
{
await Task.Run(() =>
{
Console.WriteLine("Starting...");
Thread.Sleep(1000);
Console.WriteLine("Ending...");
});
}
}
Entonces tengo algún tipo de método para leer una cadena y convertirla en un comando:
public async Task ExecuteAsync(string commandName)
{
... get command
await command.ExecuteAsync();
}
Y finalmente tengo un bucle
List<string> commands = new List<string>
{
"concreteCommand",
"wait",
"concreteCommand"
};
foreach (String command in commands)
{
await commandDispatcher.ExecuteAsync(command);
}
Esto es lo que imprime.
Starting...
Ending...
Sleeping...
Waking up...
Starting...
Ending...
Lo que significa que no se ejecuta de forma asincrónica. ¿Qué hay de malo en mi implementación?
Respuestas
Aunque no hay nada asincrónico en Thread.Sleep(1000)el motivo descrito en la pregunta, el comportamiento está en el awaitinterior de la foreachdeclaración:
foreach (String command in commands)
{
await commandDispatcher.ExecuteAsync(command);
}
Básicamente, comienza y espera el final de cada comando antes de continuar con el siguiente. Cámbielo a:
await Task.WhenAll(commands.Select(command => commandDispatcher.ExecuteAsync(command)));
Y obtendrá un resultado cercano a lo esperado en los comentarios (aunque el orden de las dos primeras declaraciones puede diferir).
Tenga WaitCommanden cuenta que en este caso evitará que se inicien los siguientes comandos. Si ese es el objetivo, diría que, desde el punto de vista del rendimiento, sería mejor refactorizar el código de alguna manera para no consumir subprocesos y CPU solo para esperar. Por ejemplo, algo como esto (código de ejemplo, tal vez se necesiten algunas correcciones para compilarlo):
List<string> commands = new List<string>
{
"concreteCommand",
"wait",
"concreteCommand"
};
await commandDispatcher.ExecuteAsync(commands);
// Dispatcher
public async Task ExecuteAsync(IEnumerable<string> commands)
{
foreach (String commandName in commands)
{
//... get command
if(command is WaitCommand)
{
await Task.Delay(5000); // or read from `WaitCommand` props
// or use Task.Delay in WaitCommand and await command here
}
else
{
_ = command.ExecuteAsync(); // fire and forget which is usually not recommended
// or add to some collection and await Task.WhenAll(...) after the loop
}
}
}
public class ConcreteCommand : ICommand
{
public async Task ExecuteAsync(List<string> arguments)
{
// this await causes the caller of ExecuteAsync
// to wait for the result of Task.Run
await Task.Run(() =>
{
Console.WriteLine("Starting...");
Thread.Sleep(1000);
Console.WriteLine("Ending...");
});
}
}
Por cierto, Thread.Sleepes súper vieja escuela y malvada. Ata el hilo y los hilos son recursos costosos. Esto confunde al grupo de subprocesos sobre cuántos subprocesos tiene disponibles y lo obliga a crear otros nuevos. Úselo en su await Task.Delaylugar.