Führen Sie zwei asynchrone Aufgaben parallel aus und sammeln Sie Ergebnisse in .NET 4.5

116

Ich habe eine Weile versucht, etwas zu bekommen, von dem ich dachte, dass es einfach wäre, mit .NET 4.5 zu arbeiten

Ich möchte zwei lange laufende Aufgaben gleichzeitig starten und die
Ergebnisse auf die beste C # 4.5 (RTM) -Methode sammeln

Folgendes funktioniert, aber ich mag es nicht, weil:

  • Ich möchte Sleepeine asynchrone Methode sein, damit es auch awaitandere Methoden können
  • Es sieht nur ungeschickt aus Task.Run()
  • Ich denke nicht, dass dies überhaupt neue Sprachfunktionen verwendet!

Arbeitscode:

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

Nicht funktionierender Code:

Update: Dies funktioniert tatsächlich und ist der richtige Weg, das einzige Problem ist das Thread.Sleep

Dieser Code funktioniert nicht, da der Aufruf zum Sleep(5000)sofortigen Starten der ausgeführten Aufgabe Sleep(1000)erst nach Abschluss ausgeführt wird. Dies ist wahr, obwohl es Sleepist asyncund ich nicht benutzeawait oder .Resultzu früh anrufe.

Ich dachte, es gibt vielleicht eine Möglichkeit, Task<T>durch Aufrufen einer asyncMethode eine Nicht-Ausführung zu erreichen , damit ich dann Start()die beiden Aufgaben aufrufen kann, aber ich kann nicht herausfinden, wie ich Task<T>durch Aufrufen einer asynchronen Methode eine erhalten kann.

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
quelle
Hinweis: Go zu einer asynchronen Methode zu machen macht keinen Unterschied
Simon_Weaver
3
Die Blockierung erfolgt um task1.Resultnicht um, var task1 = Sleep(5000)da Ihre Sleep-Methode ohne ein Schlüsselwort "wait" synchron ist.
Arvis

Antworten:

86

Sie sollten Task.Delay anstelle von Sleep für die asynchrone Programmierung verwenden und dann Task.WhenAll verwenden, um die Aufgabenergebnisse zu kombinieren. Die Aufgaben würden parallel ausgeführt.

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
quelle
11
Dies ist eine großartige Antwort ... aber ich dachte, diese falsche Antwort wäre, bis ich sie ausgeführt habe. dann habe ich verstanden. Es wird wirklich in 5 Sekunden ausgeführt. Der Trick besteht darin, die Aufgaben NICHT sofort abzuwarten, sondern auf 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
quelle
2
Ich +1, weil Sie t1, t2 als Aufgabe deklarieren, was der richtige Weg ist.
Minime
12
Ich glaube, diese Lösung erfordert, dass die Go-Methode auch asynchron ist, was bedeutet, dass sie asynchron sein kann. Wenn Sie etwas ähnlicheres wie den Fall des Fragestellers wünschen, bei dem die GoMethode des Aufrufers synchron ist, aber zwei unabhängige Aufgaben asynchron ausführen möchten (dh keine muss vor der anderen ausgeführt werden, aber beide müssen ausgeführt werden, bevor die Ausführung fortgesetzt wird), ist Task.WaitAlldies besser, und Sie tun dies nicht Das Schlüsselwort await wird nicht benötigt, daher muss die aufrufende GoMethode selbst nicht asynchron sein. Keiner der beiden Ansätze ist besser, es ist nur eine Frage Ihres Ziels.
AaronLS
1
Void-Methode: async void LongTask1() {...}hat keine Task.Result-Eigenschaft. Verwenden Sie in diesem Fall Task ohne T : async Task LongTask1().
Arvis
Ich habe die Ergebnisse von keiner der Aufgaben erhalten. Also habe ich es geändert Task<TResult> t1 = LongTask1();und jetzt bekomme ich t1.Result. <TResult>ist der Rückgabetyp Ihres Ergebnisses. Sie benötigen eine return <TResult>in Ihrer Methode, damit dies funktioniert.
Gilu
1
Es kann erwähnenswert sein, dass Sie verwenden können, wenn Sie einige wirklich einfache Dinge tun und das Extra t1und die t2Variablen nicht möchten new Task(...). Zum Beispiel : int int1 = 0; await Task.WhenAll(new Task(async () => { int1 = await LongTask1(); }));. Ein Haken dieses Ansatzes ist, dass der Compiler nicht erkennt, dass die Variable zugewiesen wurde, und sie als nicht zugewiesen behandelt, wenn Sie ihr keinen Anfangswert geben.
Robert Dennis
3

Während Ihre SleepMethode asynchron ist,Thread.Sleep ist dies nicht der . Die ganze Idee von Async besteht darin, einen einzelnen Thread wiederzuverwenden und nicht mehrere Threads zu starten. Da Sie die Verwendung eines synchronen Aufrufs von Thread.Sleep blockiert haben, funktioniert dies nicht.

Ich gehe davon aus Thread.Sleep eine Vereinfachung dessen ist, was Sie tatsächlich tun möchten. Kann Ihre tatsächliche Implementierung als asynchrone Methode codiert werden?

Wenn Sie mehrere synchrone Blockierungsanrufe ausführen müssen, schauen Sie woanders, denke ich!

Richard
quelle
danke Richard - ja, es scheint wie erwartet zu funktionieren, wenn ich meinen Serviceabruf tatsächlich benutze
Simon_Weaver
Wie läuft man dann asynchron? Ich habe eine Anwendung, die viel Datei wechselt und auf eine Datei wartet, ungefähr 5 Sekunden, und dann einen anderen Prozess, wenn ich "wann für alle" zuerst zuerst, dann zweitens ausgeführt werde, obwohl ich sagte: var x = y()und nicht var x=await y()oder y().wait()noch nicht Warten Sie den ganzen Weg, und wenn Async das nicht alleine erledigt, was soll ich dann tun? HINWEIS: y ist mit Async dekoriert, und ich erwarte, dass es alles innerhalb des Wann-Alls erledigt, nicht genau dort, wo es zugewiesen ist. BEARBEITEN: Ich habe gerade zu meinem Partner gesagt, lass es uns versuchen Task.Factory, und er sagte, es hat funktioniert, wenn ich ausbreche Seite dieser Klasse
deadManN
2

Um diesen Punkt zu beantworten:

Ich möchte, dass Sleep eine asynchrone Methode ist, damit sie auf andere Methoden warten kann

Sie können die SleepFunktion möglicherweise folgendermaßen umschreiben :

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

Wenn Sie diesen Code ausführen, wird Folgendes ausgegeben:

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

Es ist jetzt Wochenende !

    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
quelle
0

Dieser Artikel hat viele Dinge erklärt. Es ist im FAQ-Stil.

Async / Await FAQ

Dieser Teil erklärt, warum Thread.Sleepauf demselben ursprünglichen Thread ausgeführt wird - was zu meiner anfänglichen Verwirrung führt.

Verursacht das Schlüsselwort "async" den Aufruf einer Methode, die in die Warteschlange des ThreadPool gestellt wird? So erstellen Sie einen neuen Thread? Ein Raketenschiff zum Mars starten?

Nein. Und nein. Siehe die vorherigen Fragen. Das Schlüsselwort "async" gibt dem Compiler an, dass "await" innerhalb der Methode verwendet werden kann, sodass die Methode an einem Wartepunkt angehalten und ihre Ausführung asynchron fortgesetzt werden kann, wenn die erwartete Instanz abgeschlossen ist. Aus diesem Grund gibt der Compiler eine Warnung aus, wenn in einer als "asynchron" gekennzeichneten Methode keine "Wartezeiten" vorhanden sind.

Simon_Weaver
quelle