Tengo una serie de pruebas para ejecutar en hardware externo. Puedo ejecutarlos sin problemas. Debido a que todas estas pruebas tienen efectos secundarios, me gustaría poder mezclarlas y ejecutarlas una y otra vez. Cuando los pongo en una lista y trato de esperar a cada uno, todos corren en paralelo. Este no es mi entendimiento de cómo debería funcionar. Nuevamente, me gustaría poder mezclar la lista y luego ejecutarlos todos en orden.

private async void RunVisca()
{
  Ptz ptz = new Ptz(SelectedComPort, (byte)(1), SelectedBaudRate, Node.DeviceType.PTZ);

  UpdateResults("VISCA TEST START\n\n");

// Method 1: This works fine
  await VTestDriveClosedLoop(ptz, true);
  await VTestDriveClosedLoop(ptz, false);
  await VTestLimitsCl(ptz, 125, 20);
  await VTestLimitsOl(ptz, 125, 20);

// Method 2: this doesn't work
  List<Task<bool>> tests = new List<Task<bool>>();
  tests.Add(VTestDriveClosedLoop(ptz, true));
  tests.Add(VTestDriveClosedLoop(ptz, false));
  tests.Add(VTestLimitsCl(ptz, 125, 20));
  tests.Add(VTestLimitsOl(ptz, 125, 20));
        
// Tasks all run in parallel here--seems like they should run in order.
    foreach(Task<bool> test in tests)
    {
        _ = await test.ConfigureAwait(true);
    }

    UpdateResults("\nTEST COMPLETE\n\n");
 }
2
Major Major 5 oct. 2021 a las 00:41

5 respuestas

La mejor respuesta

Patrón de patrón asíncrono basado en tareas (TAP) significa que llamar a un método async, que devuelve un Task, devolverá una tarea activa .

Ya se ha iniciado un Task en estado caliente. Esto se puede comprobar comprobando la propiedad Task.Status, que nunca será TaskStatus.Created.

Una tarea fría tendrá una propiedad Task.Status de TaskStatus.Created y solo comenzará a ejecutarse cuando Start() se llama en la instancia.

La única forma de crear una tarea fría es usar los respectivos constructores públicos de Task / Task<T>.


Dado que sus tareas son devueltas por un método asincrónico, ya estarán en ejecución cuando se le devuelvan.

Para ejecutarlos en orden y no en paralelo, no debe crearlos uno después del otro a menos que se haya completado la tarea anterior. Para garantizar esto, tendrá que await por separado.

Esto da como resultado que el método 1 sea la forma correcta de ejecutar tareas activas en orden y no en paralelo.

await VTestDriveClosedLoop(ptz, true);
await VTestDriveClosedLoop(ptz, false);
await VTestLimitsCl(ptz, 125, 20);
await VTestLimitsOl(ptz, 125, 20);

El método 2/3 es incorrecto ya que ejecutará sus tareas en paralelo.

Está agregando tareas calientes devueltas de sus métodos async a su lista, sin esperarlas. Ya habrán comenzado a ejecutarse antes de que usted haya continuado para agregar la siguiente tarea a la lista.

La primera tarea no está en espera , por lo que la segunda tarea comenzará inmediatamente después, independientemente de si la tarea 1 terminó o no.

La segunda tarea no se espera, por lo que la tercera tarea, etc.

Con el método 2/3, siempre se ejecutarán en paralelo.

tests.Add(VTestDriveClosedLoop(ptz, true));
tests.Add(VTestDriveClosedLoop(ptz, false));

La verdadera pregunta es cómo ejecutar tareas en frío para que podamos almacenarlas en una lista y aprovechar la ejecución diferida hasta que recorramos nuestra lista.

La solución es almacenar delegados en su lugar , lo que activará el siguiente constructor de tareas:

public Task (Func<TResult> function);

Esto creará tareas en frío, lo que le permitirá ejecutar Task cuando se invoca Func<Task<bool>>:

var tests = new List<Func<Task<bool>>>();
tests.Add(() => VTestDriveClosedLoop(ptz, true));
tests.Add(() => VTestDriveClosedLoop(ptz, false));
tests.Add(() => VTestLimitsCl(ptz, 125, 20));
tests.Add(() => VTestLimitsOl(ptz, 125, 20));
    
foreach (var test in tests) {
     await test();
}

Este programa de demostración a continuación mostrará claramente las tareas que se ejecutan en orden frente a en paralelo.

class Program
{
    private static async Task Main(string[] args)
    {
        // 1... 2... 3... 4...
        var tasksThatRunInOrder = new List<Func<Task<bool>>>();
        tasksThatRunInOrder.Add(() => Test("1"));
        tasksThatRunInOrder.Add(() => Test("2"));
        tasksThatRunInOrder.Add(() => Test("3"));
        tasksThatRunInOrder.Add(() => Test("4"));

        foreach (var test in tasksThatRunInOrder)
            await test();

        // 1, 2, 3, 4
        var testsThatRunInParallel = new List<Task<bool>>();
        testsThatRunInParallel.Add(Test("1"));
        testsThatRunInParallel.Add(Test("2"));
        testsThatRunInParallel.Add(Test("3"));
        testsThatRunInParallel.Add(Test("4"));

        foreach (var test in testsThatRunInParallel)
            await test;
    }

    private static async Task<bool> Test(string x)
    {
        Console.WriteLine(x);
        await Task.Delay(1000);
        return true;
    }
}
4
Ermiya Eskandary 5 oct. 2021 a las 00:07

Las tareas comienzan cuando se crean. Dado que los está creando todos al mismo tiempo sin esperar a ninguno de ellos, se ejecutarán de manera paralela indeterminada. Creo que tal vez quisieras almacenar delegados en su lugar. Estos no se ejecutan hasta que se invocan.

var tests = new List<Func<Task<bool>>>();
tests.Add( () => VTestDriveClosedLoop(ptz, true) );
tests.Add( () => VTestDriveClosedLoop(ptz, false) );
tests.Add( () => VTestLimitsCl(ptz, 125, 20) );
tests.Add( () => VTestLimitsOl(ptz, 125, 20) );
    
foreach(var test in tests)
{
    _ = await test(); 
}
2
John Wu 4 oct. 2021 a las 23:06

Puede crear una lista en la que aleatorice el orden en el que desea ejecutar las cosas y luego llame a las funciones que crean las tareas en consecuencia. Por ejemplo:

    static async Task RunTasks()
    {
        Random rng = new Random();
        var order = new List<int>() { 1, 2 };
        var shuffledOrder = order.OrderBy(a => rng.Next()).ToList();

        foreach (var i in shuffledOrder)
        {
            switch (i)
            {
            case 1:
                await LoadFile1();
                break;
            case 2:
                await LoadFile2();
                break;
            }
        }
    }
-1
Mustafa Ozturk 4 oct. 2021 a las 23:05

¿Qué tal la cadena de llamadas de ContinueWith?

Task.Run(async () => 
{
    await VTestDriveClosedLoop(ptz, true);
}).ContinueWith(async (_) => 
{ 
    await VTestDriveClosedLoop(ptz, false);
}).ContinueWith(async (_) => 
{ 
    await VTestLimitsCl(ptz, 125, 20);
}).ContinueWith(async (_) => 
{ 
    await VTestLimitsOl(ptz, 125, 20);
});
-2
Auditive 4 oct. 2021 a las 22:08

Intente esperar sin asignación a una variable desechable.

-2
xofz 4 oct. 2021 a las 23:06