Unterschied zwischen TPL & async / await (Thread-Behandlung)

72

Der Versuch, den Unterschied zwischen der TPL & zu verstehen async / oder awaitder Thread-Erstellung .

Ich glaube die TPL (TaskFactory.StartNew ) ähnlich funktioniert, ThreadPool.QueueUserWorkItemda es die Arbeit an einem Thread im Thread-Pool in die Warteschlange stellt. Das ist natürlich so, es sei denn, Sie verwenden TaskCreationOptions.LongRunningeinen neuen Thread.

ich dachte async / awaitwürde so wesentlich funktionieren:

TPL:

Factory.StartNew( () => DoSomeAsyncWork() )
.ContinueWith( 
    (antecedent) => {
        DoSomeWorkAfter(); 
    },TaskScheduler.FromCurrentSynchronizationContext());

Async/.Await :

await DoSomeAsyncWork();  
DoSomeWorkAfter();

wäre identisch. Nach dem, was ich gelesen habe, scheint es, dass async/ awaitnur "manchmal" ein neuer Thread erstellt wird. Wann wird ein neuer Thread erstellt und wann wird kein neuer Thread erstellt? Wenn Sie sich mit E / A-Abschlussports befassen, kann ich sehen, dass kein neuer Thread erstellt werden muss, aber ansonsten würde ich denken, dass dies erforderlich ist. Ich denke, mein Verständnis von FromCurrentSynchronizationContextimmer war auch ein bisschen verschwommen. Ich habe immer gesagt, dass es im Wesentlichen der UI-Thread war.

Codierung4fun
quelle
4
Tatsächlich garantiert TaskCreationOptions.LongRunning keinen "neuen Thread". Pro MSDN bietet die Option "LongRunning" nur einen Hinweis für den Scheduler. Es wird kein dedizierter Thread garantiert. Ich habe das auf die harte Tour herausgefunden.
eduncan911
@ eduncan911 Obwohl das, was Sie über die Dokumentation sagen, korrekt ist, habe ich vor einiger Zeit den TPL-Quellcode nachgeschlagen und bin mir ziemlich sicher, dass tatsächlich immer ein neuer dedizierter Thread erstellt wird, wenn dies TaskCreationOptions.LongRunningangegeben wird.
Zaid Masud
@ZaidMasud: Vielleicht möchten Sie einen anderen Blick darauf werfen. Ich weiß, dass die Threads zusammengefasst wurden, weil Thread.CurrentThread.IsThreadPoolThreadsie für kurz laufende Threads von einigen hundert Millisekunden wahr waren. Ganz zu schweigen von den ThreadStatic-Variablen, die ich verwendet habe, um in mehrere Threads zu bluten, was zu allerlei Chaos führte. Ich musste meinen Code zwingen, mehrere Threads neu zu erstellen, wie es in der alten Schule üblich war, um einen dedizierten Thread zu gewährleisten. Mit anderen Worten, ich konnte die TaskFactory nicht für dedizierte Threads verwenden. Optional können Sie einen eigenen implementieren TaskScheduler, der immer einen dedizierten Thread zurückgibt.
eduncan911

Antworten:

85

Ich glaube, dass die TPL (TaskFactory.Startnew) ähnlich wie ThreadPool.QueueUserWorkItem funktioniert, da sie die Arbeit an einem Thread im Thread-Pool in die Warteschlange stellt.

So ziemlich .

Nach dem, was ich gelesen habe, scheint es, als würde async / wait nur "manchmal" einen neuen Thread erstellen.

Eigentlich tut es das nie. Wenn Sie Multithreading möchten, müssen Sie es selbst implementieren. Es gibt eine neue Task.RunMethode, die nur eine Abkürzung ist Task.Factory.StartNew, und sie ist wahrscheinlich die häufigste Methode, um eine Aufgabe im Thread-Pool zu starten.

Wenn Sie sich mit E / A-Abschlussports befassen, kann ich sehen, dass kein neuer Thread erstellt werden muss, aber ansonsten würde ich denken, dass dies erforderlich ist.

Bingo. Methoden wie Stream.ReadAsyncerstellen also tatsächlich einen TaskWrapper um ein IOCP (wenn dasStream einen IOCP hat).

Sie können auch einige Nicht-E / A- und Nicht-CPU- "Aufgaben" erstellen. Ein einfaches Beispiel ist Task.Delay, dass eine Aufgabe zurückgegeben wird, die nach einiger Zeit abgeschlossen ist.

Das Coole an async/ awaitist, dass Sie einige Arbeiten in den Thread-Pool einreihen können (z. B. Task.Run), eine E / A-gebundene Operation ausführen (z. B. Stream.ReadAsync) und eine andere Operation ausführen können (z. B. Task.Delay) ... und sie sind alle Aufgaben! Sie können erwartet oder in Kombinationen wie verwendet werden Task.WhenAll.

Jede Methode, die zurückgibt, Taskkann awaitbearbeitet werden - es muss keine asyncMethode sein. So Task.Delayund I / O-gebundene Operationen verwenden nur TaskCompletionSourcezu erstellen und zu einer Aufgabe - das einzige , was auf dem Thread - Pool durchgeführt wird , ist der eigentliche Aufgabe Abschluss , wenn das Ereignis eintritt (Timeout, I / O - Abschluss, usw.).

Ich denke, mein Verständnis von FromCurrentSynchronizationContext war auch immer ein bisschen verschwommen. Ich habe immer gesagt, dass es im Wesentlichen der UI-Thread war.

Ich habe einen Artikel über geschrieben SynchronizationContext. Meistens SynchronizationContext.Current:

  • ist ein UI-Kontext, wenn der aktuelle Thread ein UI-Thread ist.
  • ist ein ASP.NET-Anforderungskontext, wenn der aktuelle Thread eine ASP.NET-Anforderung bearbeitet.
  • ist sonst ein Thread-Pool-Kontext.

Jeder Thread kann seinen eigenen Thread festlegen SynchronizationContext, daher gibt es Ausnahmen zu den oben genannten Regeln.

Beachten Sie, dass der Standard- TaskKellner den Rest der asyncMethode für die aktuelle Methode plant, SynchronizationContext wenn sie nicht null ist . sonst geht es auf den Strom TaskScheduler. Dies ist heute nicht so wichtig, aber in naher Zukunft wird es eine wichtige Unterscheidung sein.

Ich habe mein eigenes async/ awaitIntro in meinem Blog geschrieben und Stephen Toub hat kürzlich eine exzellente async/ awaitFAQ gepostet .

Informationen zu "Parallelität" und "Multithreading" finden Sie in dieser verwandten SO-Frage . Ich würde sagen, asyncaktiviert Parallelität, die Multithreading sein kann oder nicht. Es ist einfach zu verwenden await Task.WhenAlloder await Task.WhenAnygleichzeitig zu verarbeiten. Wenn Sie den Thread-Pool nicht explizit verwenden (z. B. Task.Runoder ConfigureAwait(false)), können mehrere gleichzeitige Vorgänge gleichzeitig ausgeführt werden (z. B. mehrere E / A oder andere Arten wie Delay). und es wird kein Thread für sie benötigt. Ich verwende den Begriff "Single-Threaded-Parallelität" für diese Art von Szenario, obwohl in einem ASP.NET-Host tatsächlich " Null- Threaded-Parallelität" auftreten kann. Welches ist ziemlich süß.

Stephen Cleary
quelle
2
Gute Antwort. Ich würde auch infoq.com/articles/Async-API-Design und diese hervorragende Präsentation empfehlen : channel9.msdn.com/Events/TechEd/Europe/2013/DEV-B318 .
Philippe
Erster Link ist tot.
Felipe Deveza
"async / await FAQ" Link ist tot
Artemious
Jep. Microsoft hat ein paar Dinge verschoben und so ziemlich alle Links zu ihren Inhalten unterbrochen. Diese Antwort akzeptiert PRs.
Stephen Cleary
9

async / await vereinfacht grundsätzlich die ContinueWithMethoden (Continuations in Continuation Passing Style )

Es wird keine Parallelität eingeführt - Sie müssen dies immer noch selbst tun (oder die Async-Version einer Framework-Methode verwenden.)

Die C # 5-Version wäre also:

await Task.Run( () => DoSomeAsyncWork() );
DoSomeWorkAfter();
Nicholas Butler
quelle
Wo läuft DoSomeAsyncWork (Async / Wait-Version) in meinem obigen Beispiel? Wenn es auf dem UI-Thread läuft, wie blockiert es nicht?
Codierung4fun
1
Ihr DoSomeWorkAsync()Wartebeispiel wird nicht kompiliert, wenn Rückgaben ungültig sind oder etwas, das nicht erwartet werden kann. In Ihrem ersten Beispiel habe ich angenommen, dass es sich um eine sequentielle Methode handelt, die Sie auf einem anderen Thread ausführen möchten. Wenn Sie es so ändern, dass es a zurückgibt Task, ohne Parallelität einzuführen, wird es blockiert. In dem Sinne, dass es sequentiell ausgeführt wird und genau wie normaler Code im UI-Thread ist. awaitergibt nur, wenn die Methode eine Wartezeit zurückgibt, die noch nicht abgeschlossen ist.
Nicholas Butler
Nun, ich würde nicht sagen, dass es dort läuft, wo es läuft. Sie haben Task.Run verwendet, um den Code in DoSomeAsyncWork auszuführen. In diesem Fall wird Ihre Arbeit also an einem Threadpool-Thread ausgeführt.
Michael Ray Lovett
Ich habe die Prägnanz Ihrer Antwort geliebt.
RBT