Esegui due attività asincrone in parallelo e raccogli i risultati in .NET 4.5


116

Ho provato per un po 'di tempo a ottenere qualcosa che pensavo sarebbe stato semplice lavorare con .NET 4.5

Voglio lanciare due attività a lunga esecuzione contemporaneamente e raccogliere i
risultati nel miglior modo C # 4.5 (RTM)

Il seguente funziona ma non mi piace perché:

  • Voglio Sleepessere un metodo asincrono in modo che possa utilizzare awaitaltri metodi
  • Sembra solo goffo con Task.Run()
  • Non penso che questo stia nemmeno usando alcuna nuova funzionalità del linguaggio!

Codice di lavoro:

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;
}

Codice non funzionante:

Aggiornamento: funziona davvero ed è il modo corretto per farlo, l'unico problema è il file Thread.Sleep

Questo codice non funziona perché la chiamata a Sleep(5000)avvia immediatamente l'esecuzione dell'attività, quindi Sleep(1000)non viene eseguita fino al completamento. Questo è vero anche se lo Sleepè asynce non sto usando awaito chiamando .Resulttroppo presto.

Ho pensato che forse ci fosse un modo per ottenere un non in esecuzione Task<T>chiamando un asyncmetodo in modo da poter quindi chiamare Start()le due attività, ma non riesco a capire come ottenerlo Task<T>chiamando un metodo asincrono.

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;
}

nota: rendere Go un metodo asincrono non fa differenza
Simon_Weaver

3
Il blocco sta accadendo alle task1.Resultnon alle var task1 = Sleep(5000)perché il tuo metodo Sleep senza una parola chiave await è sincrono.
Arvis

Risposte:


86

È necessario utilizzare Task.Delay invece di Sleep per la programmazione asincrona e quindi utilizzare Task.WhenAll per combinare i risultati dell'attività. Le attività verranno eseguite in parallelo.

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;
        }
    }

11
Questa è un'ottima risposta ... ma ho pensato che fosse una risposta sbagliata finché non l'ho eseguita. poi ho capito. Si esegue davvero in 5 secondi. Il trucco è NON attendere immediatamente le attività, ma attendere invece 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
}

2
Io +1 perché dichiari t1, t2 come Task, che è il modo giusto.
Minime

12
Credo che questa soluzione richieda che anche il metodo Go sia asincrono, il che significa che espone la capacità di essere asincrono. Se si desidera qualcosa di più simile al caso di chi chiede in cui il Gometodo del chiamante è sincrono, ma si desidera completare due attività indipendenti in modo asincrono (ovvero nessuna delle due deve essere completata prima dell'altra, ma entrambe devono essere completate prima che le esecuzioni continuino), allora Task.WaitAllsarebbe meglio, e tu don non è necessaria la parola chiave await, quindi non è necessario che il Gometodo chiamante sia asincrono. Nessuno dei due approcci è migliore, è solo una questione di quale sia il tuo obiettivo.
AaronLS

1
Void methode: async void LongTask1() {...}non ha alcuna proprietà Task.Result. Utilizzare Task senza T in tal caso: async Task LongTask1().
Arvis

Non ho ottenuto i risultati da nessuna delle attività. Quindi l'ho cambiato in Task<TResult> t1 = LongTask1();e ora ottengo t1.Result. <TResult>è il tipo di ritorno del risultato. Avrai bisogno di un return <TResult>tuo metodo perché funzioni.
Gilu

1
Può valere la pena ricordare che se stai facendo alcune cose davvero semplici e non vuoi l'extra t1e le t2variabili, puoi usare new Task(...). Ad esempio: int int1 = 0; await Task.WhenAll(new Task(async () => { int1 = await LongTask1(); }));. Un problema di questo approccio è che il compilatore non riconoscerà che la variabile è stata assegnata e la tratterà come non assegnata se non le si assegna un valore iniziale.
Robert Dennis

3

Anche se il tuo Sleepmetodo è asincrono, Thread.Sleepnon lo è. L'intera idea di async è riutilizzare un singolo thread, non avviare più thread. Poiché hai bloccato l'utilizzo di una chiamata sincrona a Thread.Sleep, non funzionerà.

Presumo che Thread.Sleepsia una semplificazione di ciò che vuoi effettivamente fare. La tua implementazione effettiva può essere codificata come metodi asincroni?

Se hai bisogno di eseguire più chiamate di blocco sincrone, guarda altrove, penso!


grazie Richard - sì, sembra funzionare come previsto quando effettivamente uso la mia chiamata di servizio
Simon_Weaver

allora come eseguire async? Ho un'applicazione che fa molti cambi di file e attendo il file, circa 5 secondi, e poi un altro processo, quando "quando per tutti" viene eseguito prima per primo, poi per secondo, anche se ho detto:, var x = y()e non var x=await y()o y().wait()ancora ancora aspetta fino in fondo, e se async non lo gestisce da solo, cosa dovrei fare? NOTA che y è decorato con asincrono, e mi aspetto che faccia tutto entro quando tutto, non proprio su dove è assegnato, MODIFICA: ho appena detto al mio partner, proviamo Task.Factory, e ha detto che ha funzionato quando scoppio lato di questa classe
deadManN

2

Per rispondere a questo punto:

Voglio che Sleep sia un metodo asincrono in modo che possa attendere altri metodi

puoi forse riscrivere la Sleepfunzione in questo modo:

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();
}

l'esecuzione di questo codice produrrà:

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

2

È il fine settimana adesso!

    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;
    }

0

Questo articolo ha aiutato a spiegare molte cose. È in stile FAQ.

Domande frequenti su Async / Await

Questa parte spiega perché Thread.Sleepgira sullo stesso thread originale, portando alla mia confusione iniziale.

La parola chiave "async" fa sì che il richiamo di un metodo venga messo in coda nel ThreadPool? Per creare un nuovo thread? Per lanciare un razzo su Marte?

No. No. E no. Vedi le domande precedenti. La parola chiave "async" indica al compilatore che "await" può essere utilizzato all'interno del metodo, in modo tale che il metodo possa sospendere in un punto di attesa e riprendere la sua esecuzione in modo asincrono al termine dell'istanza attesa. Questo è il motivo per cui il compilatore emette un avviso se non ci sono "attese" all'interno di un metodo contrassegnato come "asincrono".

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.