web-dev-qa-db-ger.com

Task.WaitAll wartet nicht auf den Abschluss der Aufgabe

Bei dem Versuch, die neue (ohnehin vielleicht nicht so neue, aber für mich neue) Task asynchrone Programmierung in C # herauszufinden, bin ich auf ein Problem gestoßen, das mich etwas herausgefunden hat, und ich bin mir nicht sicher, warum.

Ich habe das Problem behoben, bin aber immer noch nicht sicher, warum es anfangs ein Problem war. Ich dachte nur, ich würde meine Erfahrungen teilen, falls jemand da draußen in die gleiche Situation gerät.

Wenn ein Gurus mich über die Ursache des Problems informieren möchte, wäre das wunderbar und sehr geschätzt. Ich mag es immer zu wissen, nur warum etwas nicht funktioniert!

Ich habe eine Testaufgabe wie folgt gemacht:

Random rng = new Random((int)DateTime.UtcNow.Ticks);
int delay = rng.Next(1500, 15000);
Task<Task<object>> testTask = Task.Factory.StartNew<Task<object>>(
    async (obj) =>
        {
            DateTime startTime = DateTime.Now;
            Console.WriteLine("{0} - Starting test task with delay of {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (int)obj);
            await Task.Delay((int)obj);
            Console.WriteLine("{0} - Test task finished after {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (DateTime.Now - startTime).TotalMilliseconds);
            return obj;
        },
        delay
    );
Task<Task<object>>[] tasks = new Task<Task<object>>[] { testTask };

Task.WaitAll(tasks);
Console.WriteLine("{0} - Finished waiting.", DateTime.Now.ToString("h:mm:ss.ffff"));

// make console stay open till user presses enter
Console.ReadLine();

Und dann ließ ich die Anwendung laufen, um zu sehen, was sie ausspuckte. Hier ist eine Beispielausgabe:

6: 06: 15.5661 - Starten der Testaufgabe mit einer Verzögerung von 3053 ms.
6: 06: 15.5662 - Das Warten ist beendet.
6: 06: 18.5743 - Die Testaufgabe wurde nach 3063.235ms beendet.

Wie Sie sehen, hat die Task.WaitAll(tasks);-Anweisung nicht viel getan. Es dauerte insgesamt 1 Millisekunde, bevor die Ausführung fortgesetzt wurde.

Ich habe unten auf meine eigene "Frage" geantwortet - aber wie ich oben sagte - wenn jemand mehr kenntnisreich ist als ich erklären möchte, warum dies nicht funktioniert, dann bitte! (Ich denke Es könnte etwas mit der Ausführung der Methode zu tun haben, sobald sie einen await-Operator erreicht hat - und dann wieder zurückgehen, wenn das Warten abgeschlossen ist ... Aber ich bin wahrscheinlich falsch. 

14
cjk84

Sie sollten die Verwendung von Task.Factory.StartNew mit async-await vermeiden. Sie sollten stattdessen Task.Run verwenden.

Eine async-Methode gibt einen Task<T> zurück, ein asynchroner Delegat tut dies ebenfalls. Task.Factory.StartNew gibt auch einen Task<T> zurück, wobei das Ergebnis das Ergebnis des Delegate-Parameters ist. Wenn sie zusammen verwendet werden, wird ein Task<Task<T>>> zurückgegeben.

Alles, was dieser Task<Task<T>> tut, ist, den Delegaten auszuführen, bis es eine Aufgabe gibt, die zurückgegeben werden muss, dh wenn das erste Warten erreicht wird. Wenn Sie nur auf die Ausführung dieser Aufgabe warten, warten Sie nicht auf die gesamte Methode, sondern nur auf den Teil vor dem ersten Warten.

Sie können dies beheben, indem Sie Task.Unwrap verwenden, der einen Task<T> erstellt, der diesen Task<Task<T>>> darstellt:

Task<Task> wrapperTask = Task.Factory.StartNew(...);
Task actualTask = wrapperTask.Unwrap();
Task.WaitAll(actualTask);
19
i3arnon

Das Problem mit Ihrem Code besteht darin, dass zwei Aufgaben im Spiel sind. Eine davon ist das Ergebnis Ihres Task.Factory.StartNew-Aufrufs, durch den die anonyme Funktion im Threadpool ausgeführt wird. Ihre anonyme Funktion wird jedoch wiederum so kompiliert, dass sie eine verschachtelte Task erzeugt, die den Abschluss ihrer asynchronen Operationen darstellt. Wenn Sie auf Ihren Task<Task<object>> warten, warten Sie nur auf die Task outer . Um auf die innere Aufgabe zu warten, sollten Sie Task.Run anstelle von Task.Factory.StartNew verwenden, da dies Ihre innere Aufgabe automatisch auspackt:

Random rng = new Random((int)DateTime.UtcNow.Ticks);
int delay = rng.Next(1500, 15000);
Task<int> testTask = Task.Run(
    async () =>
    {
        DateTime startTime = DateTime.Now;
        Console.WriteLine("{0} - Starting test task with delay of {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), delay);
        await Task.Delay(delay);
        Console.WriteLine("{0} - Test task finished after {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (DateTime.Now - startTime).TotalMilliseconds);
        return delay;
    });
Task<int>[] tasks = new[] { testTask };

Task.WaitAll(tasks);
Console.WriteLine("{0} - Finished waiting.", DateTime.Now.ToString("h:mm:ss.ffff"));

// make console stay open till user presses enter
Console.ReadLine();
9
Douglas

Hier wartet Task.WaitAll auf die äußere Aufgabe und nicht auf die innere Aufgabe. Verwenden Sie Task.Run, um keine verschachtelten Aufgaben zu haben. Das ist die Best Practices-Lösung. Eine andere Lösung ist, auf die innere Aufgabe zu warten. Zum Beispiel:

Task<object> testTask = Task.Factory.StartNew(
    async (obj) =>
        {
            ...
        }
    ).Unwrap();

Oder:

testTask.Wait();
testTask.Result.Wait();
3
usr

Nachdem ich viel herumgepeitscht und an den Haaren gezogen hatte, entschied ich mich endlich, das asynchrone Lambda loszuwerden und einfach die System.Threading.Thread.Sleep-Methode zu verwenden, um zu sehen, ob das einen Unterschied macht.

Der neue Code endete folgendermaßen:

Random rng = new Random((int)DateTime.UtcNow.Ticks);
int delay = rng.Next(1500, 15000);
Task<object> testTask = Task.Factory.StartNew<object>(
    (obj) =>
        {
            DateTime startTime = DateTime.Now;
            Console.WriteLine("{0} - Starting test task with delay of {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (int)obj);
            System.Threading.Thread.Sleep((int)obj);
            Console.WriteLine("{0} - Test task finished after {1}ms.", DateTime.Now.ToString("h:mm:ss.ffff"), (DateTime.Now - startTime).TotalMilliseconds);
            return obj;
        },
        delay
    );
Task<object>[] tasks = new Task<object>[] { testTask };

Task.WaitAll(tasks);
Console.WriteLine("{0} - Finished waiting.", DateTime.Now.ToString("h:mm:ss.ffff"));

// make console stay open till user presses enter
Console.ReadLine();

Hinweis: Durch das Entfernen des Schlüsselworts async aus der Lambda-Methode kann der Aufgabentyp jetzt einfach Task<object> und nicht Task<Task<object>> sein. Diese Änderung ist im obigen Code zu sehen.

Und voila! Es funktionierte! Ich habe das 'Fertigwarten' erhalten. Nachricht NACH Abschluss der Aufgabe.

Leider erinnere ich mich, dass ich irgendwo gelesen habe, dass Sie System.Threading.Thread.Sleep() nicht in Ihrem Task-Code verwenden sollten. Kann mich nicht erinnern warum; Aber da es nur zum Testen war und die meisten Aufgaben tatsächlich sind etwas tun, das Zeit braucht anstatt vorgeben, etwas zu tun, das Zeit braucht , sollte dies nicht wirklich ein Problem sein.

Hoffentlich hilft das einigen Leuten. Ich bin definitiv nicht der beste Programmierer der Welt (oder sogar nahe) und mein Code ist wahrscheinlich nicht großartig, aber wenn es jemandem hilft, super! :)

Danke fürs Lesen.

Edit: Die anderen Antworten auf meine Frage erklären, warum ich das Problem hatte, und diese Antwort löste das Problem nur aus Versehen. Das Wechseln zu Thread.Sleep (x) hatte keine Auswirkung . Vielen Dank an alle, die geantwortet und mir geholfen haben!

0
cjk84