wake-up-neo.com

Was ist los mit Task.Delay (). Wait ()?

Ich bin verwirrt, warum Task.Delay().Wait() 4x mehr Zeit benötigt , dann Thread.Sleep()

Z.B. task-00 lief auf nur Thread 9 und nahm 2193ms ? Ich bin mir bewusst, dass das Sync-Warten in Aufgaben schlecht ist, da der gesamte Thread blockiert wird. Es ist nur zum Test.

Einfacher Test in der Konsolenanwendung: 

bool flag = true;
var sw = Stopwatch.StartNew();
for (int i = 0; i < 10; i++)
{
    var cntr = i;
    {
        var start = sw.ElapsedMilliseconds;
        var wait = flag ? 100 : 300;
        flag = !flag;

        Task.Run(() =>
        {
            Console.WriteLine($"task-{cntr.ToString("00")} \t ThrID: {Thread.CurrentThread.ManagedThreadId.ToString("00")},\t Wait={wait}ms, \t START: {start}ms");                     
            //Thread.Sleep(wait);
            Task.Delay(wait).Wait();
            Console.WriteLine($"task-{cntr.ToString("00")} \t ThrID: {Thread.CurrentThread.ManagedThreadId.ToString("00")},\t Wait={wait}ms, \t END: {sw.ElapsedMilliseconds}ms");
            ;
        });
    }
}
Console.ReadKey();
return;

Mit Task.Delay().Wait():
task-03 ThrID: 05, Wait = 300ms, START: 184ms
task-04 ThrID: 07, Wait = 100ms, START: 184ms
task-00 ThrID: 09, Wait = 100ms, START: 0ms
task-06 ThrID: 04, Wait = 100ms, START: 185ms
task-01 ThrID: 08, Wait = 300ms, START: 183ms
task-05 ThrID: 03, Wait = 300ms, START: 185ms
task-02 ThrID: 06, Wait = 100ms, START: 184ms
task-07 ThrID: 10, Wait = 300ms, START: 209ms
task-07 ThrID: 10, Wait = 300ms, END: 1189ms
task-08 ThrID: 12, Wait = 100ms, START: 226ms
task-09 ThrID: 10, Wait = 300ms, START: 226ms
task-09 ThrID: 10, Wait = 300ms, END: 2192ms
task-06 ThrID: 04, Wait = 100ms, END: 2193ms
task-08 ThrID: 12, Wait = 100ms, END: 2194ms
task-05 ThrID: 03, Wait = 300ms, END: 2193ms
task-03 ThrID: 05, Wait = 300ms, END: 2193ms
task-00 ThrID: 09, Wait = 100ms, END: 2193ms
task-02 ThrID: 06, Wait = 100ms, END: 2193ms
task-04 ThrID: 07, Wait = 100ms, END: 2193ms
task-01 ThrID: 08, Wait = 300ms, END: 2193ms 

Mit Thread.Sleep():
task-00 ThrID: 03, Wait = 100ms, START: 0ms
task-03 ThrID: 09, Wait = 300ms, START: 179ms
task-02 ThrID: 06, Wait = 100ms, START: 178ms
task-04 ThrID: 08, Wait = 100ms, START: 179ms
task-05 ThrID: 04, Wait = 300ms, START: 179ms
task-06 ThrID: 07, Wait = 100ms, START: 184ms
task-01 ThrID: 05, Wait = 300ms, START: 178ms
task-07 ThrID: 10, Wait = 300ms, START: 184ms
task-00 ThrID: 03, Wait = 100ms, END: 284ms
task-08 ThrID: 03, Wait = 100ms, START: 184ms
task-02 ThrID: 06, Wait = 100ms, END: 285ms
task-09 ThrID: 06, Wait = 300ms, START: 184ms
task-04 ThrID: 08, Wait = 100ms, END: 286ms
task-06 ThrID: 07, Wait = 100ms, END: 293ms
task-08 ThrID: 03, Wait = 100ms, END: 385ms
task-03 ThrID: 09, Wait = 300ms, END: 485ms
task-05 ThrID: 04, Wait = 300ms, END: 486ms
task-01 ThrID: 05, Wait = 300ms, END: 493ms
task-07 ThrID: 10, Wait = 300ms, END: 494ms
task-09 ThrID: 06, Wait = 300ms, END: 586ms 

Bearbeiten:
Mit async lambda und await ist Task.Delay() so schnell wie Thread.Sleep(), möglicherweise auch schneller (511ms).
Edit 2:
Mit ThreadPool.SetMinThreads(16, 16); arbeitet Task.Delay().Wait() so schnell wie Thread.Sleep für 10 Iterationen in der Schleife. Mit mehr Iterationen ist es wieder langsamer. Es ist auch interessant, dass, wenn ich ohne Anpassung die Anzahl der Iterationen für Thread.Sleep auf 30 erhöht, es noch schneller ist, dann 10 Iteration mit Task.Delay().Wait()
Edit 3:
Die Überladung Task.Delay(wait).Wait(wait) funktioniert so schnell wie Thread.Sleep()

10
Rekshino

Ich habe das gepostete Snippet etwas umgeschrieben, um die Ergebnisse besser zu ordnen. Mein brandneuer Laptop hat zu viele Kerne, um die vorhandene fehlerhafte Ausgabe gut genug zu interpretieren. Die Start- und Endzeiten jeder Aufgabe werden aufgezeichnet und nach Beendigung aller Aufgaben angezeigt. Und die tatsächliche Startzeit der Aufgabe aufzeichnen. Ich habe:

0: 68 - 5031
1: 69 - 5031
2: 68 - 5031
3: 69 - 5031
4: 69 - 1032
5: 68 - 5031
6: 68 - 5031
7: 69 - 5031
8: 1033 - 5031
9: 1033 - 2032
10: 2032 - 5031
11: 2032 - 3030
12: 3030 - 5031
13: 3030 - 4029
14: 4030 - 5031
15: 4030 - 5031

Ah, das macht plötzlich sehr viel Sinn. Ein Muster, auf das Sie bei Threadpool-Threads immer achten müssen. Beachten Sie, wie einmal pro Sekunde etwas Wichtiges passiert und zwei TP-Threads laufen, und einige davon können abgeschlossen werden.

Dies ist ein Deadlock-Szenario, ähnlich wie dieses Q + A - aber ansonsten ohne das katastrophalere Ergebnis des Benutzercodes. Die Ursache ist nahezu unmöglich zu erkennen, da sie in .NETFramework-Code vergraben ist. Sie müssen sehen, wie Task.Delay () implementiert wird, um einen Sinn daraus zu machen.

Der relevante Code ist hier , beachte, wie er einen System.Threading.Timer verwendet, um die Verzögerung zu implementieren. Ein gruseliges Detail dieses Timers ist, dass sein Callback im Threadpool ausgeführt wird. Welches ist der grundlegende Mechanismus, durch den Task.Delay () das Versprechen "Sie zahlen nicht für das, was Sie nicht verwenden" implementiert, kann.

Das heikle Detail ist, dass dies eine Weile dauern kann, wenn der Threadpool beschäftigt ist, um Ausführungsanforderungen an Threadpools zu schaffen. Es ist nicht der Timer ist langsam, das Problem ist nur, dass die Callback-Methode nicht früh genug beginnt. Das Problem in diesem Programm, Task.Run (), hat eine Reihe von Anforderungen hinzugefügt, mehr als gleichzeitig ausgeführt werden können. Der Deadlock tritt auf, weil der von Task.Run () gestartete TP-Thread den Wait () - Aufruf nicht abschließen kann, bis der Timer-Callback ausgeführt wird.

Sie können es zu einem harten Deadlock machen, der das Programm für immer blockiert, indem Sie dieses Bit an den Start von Main () hinzufügen:

     ThreadPool.SetMaxThreads(Environment.ProcessorCount, 1000);

Aber die normalen Max-Threads sind viel höher. Was der Threadpool-Manager ausnutzt, um diese Art von Deadlock zu lösen. Einmal pro Sekunde können zwei weitere Threads ausgeführt werden, deren "ideale" Anzahl ausgeführt wird, wenn die vorhandenen Threads nicht abgeschlossen werden. Das sehen Sie in der Ausgabe. Es sind jedoch nur zwei gleichzeitig, nicht genug, um die 8 ausgelasteten Threads, die beim Wait () - Aufruf blockiert sind, stark zu beeinträchtigen.

Der Thread.Sleep () - Aufruf hat dieses Problem nicht, er hängt nicht von .NETFramework-Code oder dem Threadpool ab. Es ist der OS-Thread-Scheduler, der sich darum kümmert, und er läuft immer aufgrund der Taktunterbrechung. Dadurch können neue tp-Threads alle 100 oder 300 ms anstatt einmal pro Sekunde ausgeführt werden.

Kaum ein konkreter Ratschlag, um eine solche Blockierfalle zu vermeiden. Anders als der allgemeine Rat sollte es immer vermieden werden, Arbeitsthreads zu blockieren.

6
Hans Passant

Weder Thread.Sleep() noch Task.Delay() garantieren, dass die internen Daten korrekt sind. 

Thread.Sleep() Arbeit Task.Delay() ganz anders. Thread.Sleep() blockiert den aktuellen Thread und verhindert, dass Code ausgeführt wird. Task.Delay() erstellt einen Timer, der nach Ablauf der Zeit ein Häkchen macht und der Ausführung im Threadpool zuweist.

Sie führen Ihren Code mit Task.Run() aus. Dadurch werden Aufgaben erstellt und in den Threadpool aufgenommen. Wenn Sie Task.Delay() verwenden, wird der aktuelle Thread wieder im Threadpool freigegeben und kann mit der Verarbeitung einer anderen Aufgabe beginnen. Auf diese Weise werden mehrere Aufgaben schneller gestartet, und Sie werden die Startzeiten für alle aufzeichnen. Wenn dann die Verzögerungstimer anfangen zu ticken, erschöpfen sie auch den Pool, und einige Aufgaben dauern länger als seit dem Start. Deshalb nehmen Sie lange Zeiten auf.

Wenn Sie Thread.Sleep() verwenden, blockieren Sie den aktuellen Thread im Pool, und es können keine weiteren Tasks verarbeitet werden. Der Thread-Pool wächst nicht sofort, daher warten neue Aufgaben. Daher werden alle Aufgaben ungefähr zur gleichen Zeit ausgeführt, was Ihnen schneller erscheint.

BEARBEITEN: Sie verwenden Task.Wait(). In Ihrem Fall versucht Task.Wait () die Ausführung in demselben Thread zu integrieren. Gleichzeitig ist Task.Delay() auf einen Zeitgeber angewiesen, der im Thread-Pool ausgeführt wird. Sobald Sie Task.Wait() aufgerufen haben, blockieren Sie einen Worker-Thread aus dem Pool. Zweitens benötigen Sie einen verfügbaren Thread für den Pool, um die Operation derselben Workermethode abzuschließen. Wenn Sie await die Delay() verwenden, ist kein solches Inlining erforderlich, und der Arbeitsthread ist sofort verfügbar, um Timerereignisse zu verarbeiten. Wenn Sie Thread.Sleep haben, haben Sie keinen Zeitgeber, um die Worker-Methode abzuschließen.

Ich glaube, dass dies den drastischen Unterschied in der Verzögerung verursacht.

4
Nick

Ihr Problem ist, dass Sie asynchronen Code mit synchronem Code mischen, ohne async und await zu verwenden. Verwenden Sie keinen synchronen Aufruf .Wait, da dies den Thread blockiert. Deshalb funktioniert asynchroner Code Task.Delay() nicht ordnungsgemäß.

Asynchroner Code funktioniert häufig nicht ordnungsgemäß, wenn er synchron aufgerufen wird, da er nicht dafür ausgelegt ist. Sie können Glück haben und asynchroner Code scheint zu funktionieren, wenn Sie synchron laufen. Wenn Sie jedoch einen externen Bibliotheksautor dieser Bibliothek verwenden, kann der Code so geändert werden, dass der Code beschädigt wird. Asynchroner Code sollte vollständig asynchron sein.

Asynchroner Code ist normalerweise langsamer als synchroner Code. Der Vorteil ist jedoch, dass er asynchron läuft. Beispiel: Wenn Ihr Code darauf wartet, dass die Datei geladen wird, kann anderer Code auf demselben CPU-Kern ausgeführt werden, während die Datei geladen wird.

Ihr Code sollte wie folgt aussehen, aber mit async können Sie nicht sicher sein, dass ManagedThreadId gleich bleibt. Weil der Thread ausgeführt wird, während der Code ausgeführt wird. Sie sollten niemals die ManagedThreadId-Eigenschaft oder das [ThreadStatic]-Attribut verwenden, wenn Sie aus diesem Grund sowieso asynchronen Code verwenden.

Async/Await - Best Practices in der asynchronen Programmierung

bool flag = true;
var sw = Stopwatch.StartNew();
for (int i = 0; i < 10; i++)
{
    var cntr = i;
    {
        var start = sw.ElapsedMilliseconds;
        var wait = flag ? 100 : 300;
        flag = !flag;

        Task.Run(async () =>
        {
            Console.WriteLine($"task-{cntr.ToString("00")} \t ThrID: {Thread.CurrentThread.ManagedThreadId.ToString("00")},\t Wait={wait}ms, \t START: {start}ms");
            await Task.Delay(wait);
            Console.WriteLine($"task-{cntr.ToString("00")} \t ThrID: {Thread.CurrentThread.ManagedThreadId.ToString("00")},\t Wait={wait}ms, \t END: {sw.ElapsedMilliseconds}ms");
        });
    }
}
Console.ReadKey();
return;
0
Wanton