Uruchom równolegle dwa zadania asynchroniczne i zbierz wyniki w .NET 4.5

116

Od jakiegoś czasu próbowałem uzyskać coś, co moim zdaniem byłoby proste przy pracy z .NET 4.5

Chcę odpalić dwa długotrwałe zadania w tym samym czasie i zebrać
wyniki w najlepszy sposób C # 4.5 (RTM)

Poniższe działa, ale mi się to nie podoba, ponieważ:

  • Chcę Sleepbyć metodą asynchroniczną, aby mogła mieć awaitinne metody
  • Po prostu wygląda niezdarnie Task.Run()
  • Nie sądzę, żeby to w ogóle korzystało z nowych funkcji językowych!

Kod roboczy:

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

Niedziałający kod:

Aktualizacja: To faktycznie działa i jest właściwy sposób, aby to zrobić, jedynym problemem jest Thread.Sleep

Ten kod nie działa, ponieważ wywołanie Sleep(5000)natychmiast uruchamia zadanie, więc Sleep(1000)nie jest uruchamiane, dopóki nie zostanie ukończone. To prawda, mimo że Sleepjest asynci nie używam awaitani nie dzwonię.Result zbyt wcześnie.

Pomyślałem, że może istnieje sposób, aby Task<T>wywołać asyncmetodę niedziałającą , wywołując metodę, aby móc następnie wywołać Start()dwa zadania, ale nie mogę dowiedzieć się, jak uzyskać a Task<T>od wywołania metody asynchronicznej.

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
źródło
uwaga: uczynienie Go metodą asynchroniczną nie robi różnicy
Simon_Weaver
3
Blokowanie dzieje się w miejscu task1.Resultnie o godzinie, var task1 = Sleep(5000)ponieważ metoda uśpienia bez słowa kluczowego await jest synchroniczna.
Arvis

Odpowiedzi:

86

Do programowania asynchronicznego należy użyć Task.Delay zamiast Sleep, a następnie użyć Task.WhenAll, aby połączyć wyniki zadania. Zadania działałyby równolegle.

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
źródło
11
To świetna odpowiedź ... ale myślałem, że to zła odpowiedź, dopóki jej nie uruchomiłem. wtedy zrozumiałem. To naprawdę działa w 5 sekund. Sztuczka polega na tym, aby NIE czekać na zadania natychmiast, zamiast tego czekać na Task.WhenAll.
Tim Lovell-Smith
114
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
źródło
2
Ja +1, ponieważ deklarujesz t1, t2 jako zadanie, co jest właściwą drogą.
Minime
12
Uważam, że to rozwiązanie wymaga, aby metoda Go była również asynchroniczna, co oznacza, że ​​ujawnia zdolność do asynchroniczności. Jeśli chciałbyś czegoś bardziej podobnego do przypadku askers, w którym Gometoda wywołującego jest synchroniczna, ale chce wykonać dwa niezależne zadania asynchronicznie (tj. Żadne z nich nie musi kończyć się przed drugim, ale oba muszą zakończyć się przed kontynuacją wykonywania), Task.WaitAllbyłoby lepiej i nie nie potrzebuje słowa kluczowego await, więc nie potrzeba, aby Gometoda wywołująca była sama asynchroniczna. Żadne z podejść nie jest lepsze, to tylko kwestia celu.
AaronLS
1
async void LongTask1() {...}Metoda unieważnienia : nie ma właściwości Task.Result. Użyj zadanie bez T w takim przypadku: async Task LongTask1().
Arvis
Nie otrzymałem wyników z żadnego z zadań. Więc zmieniłem to na Task<TResult> t1 = LongTask1();i teraz mam t1.Result. <TResult>to zwracany typ wyniku. Aby return <TResult>to zadziałało, potrzebujesz w swojej metodzie.
gilu
1
Warto wspomnieć, że jeśli robisz naprawdę proste rzeczy i nie chcesz dodatkowych t1i t2zmiennych, możesz użyć new Task(...). Na przykład: int int1 = 0; await Task.WhenAll(new Task(async () => { int1 = await LongTask1(); }));. Jednym z haczyków tego podejścia jest to, że kompilator nie rozpozna, że ​​zmienna została przypisana i potraktuje ją jako nieprzypisaną, jeśli nie nadasz jej wartości początkowej.
Robert Dennis
3

Chociaż twoja Sleepmetoda jest asynchroniczna, Thread.Sleepnie jest. Cała idea asynchronizacji polega na ponownym użyciu pojedynczego wątku, a nie na rozpoczynaniu wielu wątków. Ponieważ zablokowałeś używanie synchronicznego wywołania Thread.Sleep, to nie zadziała.

Zakładam, że Thread.Sleepjest to uproszczenie tego, co naprawdę chcesz zrobić. Czy Twoja rzeczywista implementacja może być zakodowana jako metody asynchroniczne?

Jeśli musisz uruchomić wiele synchronicznych wywołań blokujących, spójrz gdzie indziej, myślę!

Richard
źródło
dzięki Richard - tak, wygląda na to, że działa zgodnie z oczekiwaniami, kiedy faktycznie korzystam z mojego
zgłoszenia
więc jak uruchomić asynchroniczne? Mam aplikację, która często przełącza pliki i czeka na plik, około 5 sekund, a potem kolejny proces, kiedy i "kiedy na wszystko", najpierw jest uruchamiany, potem drugi, mimo że powiedziałem: var x = y()i nie, var x=await y()lub y().wait()nadal to poczekaj do końca, a jeśli asynchronizacja nie poradzi sobie sama z tym, co mam zrobić? PAMIĘTAJ, że y jest ozdobione asynchronicznie i spodziewam się, że zrobi wszystko, gdy wszystko, a nie dokładnie tam, gdzie jest przypisane, EDYTUJ: ja tylko wtedy, gdy mój partner powiedział, spróbujmy Task.Factory, a on powiedział, że zadziałało, gdy się wyrwę strona tej klasy
deadManN
2

Aby odpowiedzieć na to pytanie:

Chcę, aby funkcja Sleep była metodą asynchroniczną, aby mogła czekać na inne metody

możesz przepisać Sleepfunkcję w ten sposób:

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

uruchomienie tego kodu spowoduje wyświetlenie:

Starting
Sleeping for 2000
Sleeping for 1000
*(one second later)*
Sleeping for 1000END
*(one second later)*
Sleeping for 2000END
Slept for 3000 ms
asidis
źródło
2

Teraz jest weekend !

    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
źródło
0

Ten artykuł pomógł wyjaśnić wiele rzeczy. Jest w stylu FAQ.

Async / Await - często zadawane pytania

Ta część wyjaśnia, dlaczego Thread.Sleepdziała na tym samym oryginalnym wątku - co prowadzi do mojego początkowego zamieszania.

Czy słowo kluczowe „async” powoduje, że wywołanie metody ustawia się w kolejce do puli wątków? Aby utworzyć nowy wątek? Wystrzelić rakietę na Marsa?

Nie. Nie. I nie. Zobacz poprzednie pytania. Słowo kluczowe „async” wskazuje kompilatorowi, że „await” może być użyte wewnątrz metody, tak że metoda może zawiesić się w momencie oczekiwania i jej wykonanie zostanie wznowione asynchronicznie po zakończeniu oczekiwanej instancji. Dlatego kompilator generuje ostrzeżenie, jeśli w metodzie oznaczonej jako „async” nie ma żadnych elementów „oczekujących”.

Simon_Weaver
źródło