Ejecute dos tareas asíncronas en paralelo y recopile resultados en .NET 4.5

116

He estado intentando durante un tiempo conseguir algo que pensé que sería sencillo trabajar con .NET 4.5

Quiero iniciar dos tareas de ejecución prolongada al mismo tiempo y recopilar los
resultados de la mejor manera C # 4.5 (RTM)

Lo siguiente funciona pero no me gusta porque:

  • Quiero Sleepser un método asincrónico para que pueda awaitotros métodos
  • Simplemente se ve torpe con Task.Run()
  • ¡No creo que esto esté usando ninguna característica de idioma nueva en absoluto!

Código de trabajo:

public static void Go()
{
    Console.WriteLine("Starting");

    var task1 = Task.Run(() => Sleep(5000));    
    var task2 = Task.Run(() => Sleep(3000));

    int totalSlept = task1.Result + task2.Result;

    Console.WriteLine("Slept for a total of " + totalSlept + " ms");
}

private static int Sleep(int ms)
{
    Console.WriteLine("Sleeping for " + ms);
    Thread.Sleep(ms);
    Console.WriteLine("Sleeping for " + ms + " FINISHED");
    return ms;
}

Código que no funciona:

Actualización: esto realmente funciona y es la forma correcta de hacerlo, el único problema es el Thread.Sleep

Este código no funciona porque la llamada a Sleep(5000)inicia inmediatamente la ejecución de la tarea, por lo Sleep(1000)que no se ejecuta hasta que se completa. Esto es cierto a pesar de que lo Sleepes asyncy no estoy usando awaito llamando .Resultdemasiado pronto.

Pensé que tal vez había una manera de hacer que no se ejecutara Task<T>llamando a un asyncmétodo para luego poder llamar Start()a las dos tareas, pero no puedo averiguar cómo obtener una Task<T>llamando a un método asíncrono.

public static void Go()
{
    Console.WriteLine("Starting");

    var task1 = Sleep(5000);    // blocks
    var task2 = Sleep(1000);

    int totalSlept = task1.Result + task2.Result;

    Console.WriteLine("Slept for " + totalSlept + " ms");
}

private static async Task<int> Sleep(int ms)
{
    Console.WriteLine("Sleeping for " + ms);
    Thread.Sleep(ms);
    return ms;
}
Simon_Weaver
fuente
nota: hacer de Go un método asíncrono no hace ninguna diferencia
Simon_Weaver
3
El bloqueo ocurre en task1.Resultnot at var task1 = Sleep(5000)porque su método Sleep sin una palabra clave await es sincrónico.
Arvis

Respuestas:

86

Debe usar Task.Delay en lugar de Sleep para la programación asincrónica y luego usar Task.WhenAll para combinar los resultados de la tarea. Las tareas se ejecutarían en paralelo.

public class Program
    {
        static void Main(string[] args)
        {
            Go();
        }
        public static void Go()
        {
            GoAsync();
            Console.ReadLine();
        }
        public static async void GoAsync()
        {

            Console.WriteLine("Starting");

            var task1 = Sleep(5000);
            var task2 = Sleep(3000);

            int[] result = await Task.WhenAll(task1, task2);

            Console.WriteLine("Slept for a total of " + result.Sum() + " ms");

        }

        private async static Task<int> Sleep(int ms)
        {
            Console.WriteLine("Sleeping for {0} at {1}", ms, Environment.TickCount);
            await Task.Delay(ms);
            Console.WriteLine("Sleeping for {0} finished at {1}", ms, Environment.TickCount);
            return ms;
        }
    }
softveda
fuente
11
Esta es una gran respuesta ... pero pensé que esta respuesta incorrecta era hasta que la ejecuté. entonces entendí. Realmente se ejecuta en 5 segundos. El truco consiste en NO esperar las tareas inmediatamente, sino esperar en Task.WhenAll.
Tim Lovell-Smith
113
async Task<int> LongTask1() { 
  ...
  return 0; 
}

async Task<int> LongTask2() { 
  ...
  return 1; 
}

...
{
   Task<int> t1 = LongTask1();
   Task<int> t2 = LongTask2();
   await Task.WhenAll(t1,t2);
   //now we have t1.Result and t2.Result
}
Bart
fuente
2
I +1 porque declaras t1, t2 como Task, que es la forma correcta.
Mínimo
12
Creo que esta solución requiere que el método Go también sea asíncrono, lo que significa que expone la capacidad de ser asíncrono. Si quisiera algo más como el caso de los solicitantes en el que el Gométodo de la persona que llama es sincrónico, pero desea completar dos tareas independientes de forma asincrónica (es decir, ninguna debe completarse antes que la otra, pero ambas deben completarse antes de que continúen las ejecuciones), entonces Task.WaitAllsería mejor, y no No necesita la palabra clave await, por lo que no es necesario que el Gométodo de llamada sea ​​asíncrono. Ninguno de los enfoques es mejor, es solo una cuestión de cuál es tu objetivo.
AaronLS
1
async void LongTask1() {...}Método vacío: no tiene propiedad Task.Result. Tarea utilizar sin T en tal caso: async Task LongTask1().
Arvis
No obtuve los resultados de ninguna de las tareas. Así que lo cambié a Task<TResult> t1 = LongTask1();y ahora lo tengo t1.Result. <TResult>es el tipo de retorno de su resultado. Necesitará un return <TResult>método en su método para que esto funcione.
gilu
1
Puede valer la pena mencionar que si está haciendo cosas realmente simples y no quiere el extra t1y las t2variables, puede usar new Task(...). Por ejemplo: int int1 = 0; await Task.WhenAll(new Task(async () => { int1 = await LongTask1(); }));. Un inconveniente de este enfoque es que el compilador no reconocerá que la variable fue asignada y la tratará como no asignada si no le da un valor inicial.
Robert Dennis
3

Si bien su Sleepmétodo es asíncrono, Thread.Sleepno lo es. La idea general de async es reutilizar un solo hilo, no iniciar varios hilos. Debido a que ha bloqueado el uso de una llamada sincrónica a Thread.Sleep, no va a funcionar.

Estoy asumiendo que Thread.Sleep es una simplificación de lo que realmente quiere hacer. ¿Se puede codificar su implementación real como métodos asíncronos?

Si necesita ejecutar múltiples llamadas de bloqueo sincrónico, ¡busque en otro lado, creo!

Ricardo
fuente
gracias Richard - sí, parece funcionar como se esperaba cuando en realidad uso mi llamada de servicio
Simon_Weaver
entonces, ¿cómo ejecutar async? Tengo una aplicación que hace muchos cambios de archivo y espera el archivo, alrededor de 5 segundos, y luego otro proceso, cuando "cuando para todo" primero se ejecuta primero, luego en segundo lugar, aunque dije:, var x = y()y no var x=await y()o y().wait()todavía espere todo el tiempo, y si async no maneja eso por sí solo, ¿qué debo hacer? TENGA EN CUENTA que y está decorado con async, y espero que haga todo dentro del when all, no justo en el lugar donde está asignado, EDITAR: justo cuando le dije a mi compañero, intentemos Task.Factory, y él dijo que funcionó cuando salí lado de esta clase
deadManN
2

Para responder a este punto:

Quiero que Sleep sea un método asincrónico para que pueda esperar a otros métodos

tal vez puedas reescribir la Sleepfunción así:

private static async Task<int> Sleep(int ms)
{
    Console.WriteLine("Sleeping for " + ms);
    var task = Task.Run(() => Thread.Sleep(ms));
    await task;
    Console.WriteLine("Sleeping for " + ms + "END");
    return ms;
}

static void Main(string[] args)
{
    Console.WriteLine("Starting");

    var task1 = Sleep(2000);
    var task2 = Sleep(1000);

    int totalSlept = task1.Result +task2.Result;

    Console.WriteLine("Slept for " + totalSlept + " ms");
    Console.ReadKey();
}

ejecutar este código dará como resultado:

Starting
Sleeping for 2000
Sleeping for 1000
*(one second later)*
Sleeping for 1000END
*(one second later)*
Sleeping for 2000END
Slept for 3000 ms
asidis
fuente
2

¡Es fin de semana ahora!

    public async void Go()
    {
        Console.WriteLine("Start fosterage...");

        var t1 = Sleep(5000, "Kevin");
        var t2 = Sleep(3000, "Jerry");
        var result = await Task.WhenAll(t1, t2);

        Console.WriteLine($"My precious spare time last for only {result.Max()}ms");
        Console.WriteLine("Press any key and take same beer...");
        Console.ReadKey();
    }

    private static async Task<int> Sleep(int ms, string name)
    {
            Console.WriteLine($"{name} going to sleep for {ms}ms :)");
            await Task.Delay(ms);
            Console.WriteLine("${name} waked up after {ms}ms :(";
            return ms;
    }
Arvis
fuente
0

Este artículo ayudó a explicar muchas cosas. Tiene un estilo de preguntas frecuentes.

Preguntas frecuentes de Async / Await

Esta parte explica por qué se Thread.Sleepejecuta en el mismo hilo original, lo que lleva a mi confusión inicial.

¿La palabra clave "async" hace que la invocación de un método se ponga en cola en ThreadPool? ¿Para crear un nuevo hilo? ¿Lanzar un cohete a Marte?

No. No. Y no. Consulte las preguntas anteriores. La palabra clave "async" indica al compilador que "await" puede usarse dentro del método, de modo que el método puede suspender en un punto de espera y reanudar su ejecución de forma asíncrona cuando se completa la instancia esperada. Por eso el compilador emite una advertencia si no hay "esperas" dentro de un método marcado como "asíncrono".

Simon_Weaver
fuente