2012-04-23 12 views
50

Versuchen Sie, den Unterschied zwischen der TPL & async/zu verstehen, wenn es um Thread-Erstellung kommt.Unterschied zwischen der TPL & async/await (Threadbehandlung)

Ich glaube, die TPL (TaskFactory.Startnew) funktioniert ähnlich wie ThreadPool.QueueUserWorkItem in der Warteschlange Arbeit an einem Thread im Thread-Pool. Das ist natürlich, es sei denn, Sie verwenden TaskCreationOptions.LongRunning, die einen neuen Thread erstellen.

Ich dachte, async/await würde im wesentlichen auf ähnliche Weise so funktionieren:

TPL:

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

Async/Await:

await DoSomeAsyncWork(); 
DoSomeWorkAfter(); 

wären identisch. Nach dem, was ich gelesen habe, scheint es async/warten nur "manchmal" erstellt einen neuen Thread. Wann erstellt es einen neuen Thread und wann erstellt er keinen neuen Thread? Wenn Sie mit IO-Completion-Ports zu tun haben, kann ich sehen, dass ich keinen neuen Thread erstellen muss, aber ansonsten würde ich denken, dass es nötig wäre. Ich denke, mein Verständnis von FromCurrentSynchronizationContext war immer auch etwas unscharf. Ich war immer im Wesentlichen der UI-Thread.

+2

Tatsächlich garantiert TaskCreationOptions.LongRunning keinen "neuen Thread". Pro MSDN * bietet die Option "LongRunning" nur einen Hinweis für den Scheduler; es garantiert keinen dedizierten Thread. * Ich fand das auf die harte Tour. – eduncan911

+0

@ eduncan911 obwohl das, was Sie über die Dokumentation sagen, korrekt ist, habe ich den TPL-Quellcode eine Weile zurückgeschaut und ich bin mir ziemlich sicher, dass tatsächlich ein neuer dedizierter Thread immer dann erstellt wird, wenn 'TaskCreationOptions.LongRunning' spezifiziert ist. –

+0

@ZaidMasud: Vielleicht möchten Sie einen anderen Blick werfen.Ich weiß, dass es die Threads zusammenfasste, weil 'Thread.CurrentThread.IsThreadPoolThread' True für kurz laufende Threads von einigen hundert Millisekunden zurückgab. ganz zu schweigen von den ThreadStatic-Variablen, die ich in mehrere Threads blutete, was zu allen Arten von Havok führte. Ich musste meinen Code dazu zwingen, mehrere Threads() s neu zu schreiben, um einen dedizierten Thread zu garantieren. Mit anderen Worten, ich konnte die TaskFactory nicht für dedizierte Threads verwenden. Optional können Sie einen eigenen TaskScheduler implementieren, der immer einen dedizierten Thread zurückgibt. – eduncan911

Antwort

64

ich die TPL (TaskFactory.Startnew) glauben funktioniert ähnlich, dass ich ThreadPool.QueueUserWorkItem t in der Warteschlange Arbeit an einem Thread im Thread-Pool.

Pretty much.

Von dem, was ich gelesen habe scheint es async/warten nur "manchmal" erstellt einen neuen Thread.

Eigentlich tut es nie. Wenn Sie Multithreading haben wollen, müssen Sie es selbst implementieren. Es gibt eine neue Task.Run Methode, die nur Kurzform für Task.Factory.StartNew ist, und es ist wahrscheinlich die gebräuchlichste Art, eine Aufgabe im Thread-Pool zu starten.

Wenn Sie IO-Completion-Ports haben, kann ich sehen, dass es keinen neuen Thread erstellen muss, aber ansonsten würde ich denken, dass es nötig wäre.

Bingo. So werden Methoden wie Stream.ReadAsync tatsächlich einen Task Wrapper um einen IOCP erstellen (wenn der Stream einen IOCP hat).

Sie können auch einige Nicht-I/O-, Nicht-CPU- "Tasks" erstellen. Ein einfaches Beispiel ist Task.Delay, das eine Aufgabe zurückgibt, die nach einiger Zeit abgeschlossen ist.

Die kühle Sache über async/await ist, dass Sie etwas Arbeit an den Thread-Pool Warteschlange kann (zB Task.Run), einige der I/O-gebundenen Betrieb (zB Stream.ReadAsync), und eine andere Operation tun (zB Task.Delay) ... und sie sind alle Aufgaben! Sie können erwartet oder in Kombinationen wie Task.WhenAll verwendet werden.

Jede Methode, die Task zurückgibt, kann await ed sein - es muss keine async Methode sein. So Task.Delay und I/O-gebundene Operationen verwenden einfach TaskCompletionSource, um eine Aufgabe zu erstellen und zu vervollständigen - das einzige, was im Thread-Pool getan wird, ist die eigentliche Aufgabe abgeschlossen, wenn das Ereignis auftritt (Timeout, E/A-Abschluss, etc).

Ich denke, mein Verständnis von FromCurrentSynchronizationContext war immer auch ein bisschen unscharf. Ich war immer im Wesentlichen der UI-Thread.

Ich schrieb an article auf SynchronizationContext. Die meiste Zeit, 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 bedient.
  • ist ansonsten ein Thread-Pool-Kontext.

Jeder Thread kann gesetzt sein eigenes SynchronizationContext, so gibt es Ausnahmen von den obigen Regeln.

beachte, dass der Standard Task Erwartenden den Rest der async Methode auf die aktuellen SynchronizationContextplant, wenn es nicht null ist; ansonsten geht es auf die aktuelle TaskScheduler. Das ist heute nicht so wichtig, aber in naher Zukunft wird es eine wichtige Unterscheidung sein.

Ich schrieb meine eigene async/await intro auf meinem Blog, und Stephen Toub kürzlich gepostet eine hervorragende async/await FAQ.

Bezüglich "Nebenläufigkeit" vs "Multithreading", siehe this related SO question. Ich würde sagen, async ermöglicht Parallelität, die Multithread sein kann oder nicht. Es ist einfach await Task.WhenAll oder await Task.WhenAny für die gleichzeitige Verarbeitung zu verwenden, und wenn Sie nicht explizit den Thread-Pool verwenden (z. B. Task.Run oder ConfigureAwait(false)), können mehrere gleichzeitige Operationen gleichzeitig ausgeführt werden (z. B. mehrere E/A oder andere) Typen wie Delay) - und es wird kein Thread für sie benötigt. Ich benutze den Begriff "Single-Threaded Concurrency" für diese Art von Szenario, obwohl in einem ASP.NET-Host, können Sie tatsächlich mit "zero-Threaded Concurrency" enden. Das ist ziemlich süß.

+1

Schöne Antwort. Ich würde auch http://www.infoq.com/articles/Async-API-Design und diese ausgezeichnete Präsentation empfehlen: http://channel9.msdn.com/Events/TechEd/Europe/2013/DEV-B318. – Philippe

+0

Erste Verbindung ist tot. –

+0

@FelipeDeveza Fest, danke! –

8

async/erwarten grundsätzlich vereinfacht die ContinueWith Methoden (Fortsetzungen in Continuation Passing Style)

Dabei spielt es keine Parallelität vorstellen - man muss immer noch das tun, selbst (oder die Async-Version eines Rahmenmethode verwenden.)

Also, die C# 5 Version wäre:

await Task.Run(() => DoSomeAsyncWork()); 
DoSomeWorkAfter(); 
+0

Also wo läuft es DoSomeAsyncWork (async/Warteversion) in meinem Beispiel oben? Wenn es auf dem UI-Thread ausgeführt wird, wie blockiert es nicht? – coding4fun

+1

Ihr erwartetes Beispiel wird nicht kompiliert, wenn 'DoSomeWorkAsync()' void oder etwas zurückgibt, das nicht erwartet werden kann. In Ihrem ersten Beispiel ging ich davon aus, dass es sich um eine sequenzielle Methode handelt, die Sie in einem anderen Thread ausführen möchten. Wenn Sie es geändert haben, um eine "Aufgabe" zurückzugeben, ohne Nebenläufigkeit einzuführen, dann würde es ja blockieren. In dem Sinne, dass es sequenziell ausgeführt wird und wie normaler Code im UI-Thread ist. 'await' gibt nur dann zurück, wenn die Methode ein noch nicht abgeschlossenes erwartetes Ergebnis zurückgibt. –

+0

nun, ich würde nicht sagen, dass es läuft, wo immer es sich entscheidet zu laufen. Sie haben Task.Run verwendet, um den Code in DoSomeAsyncWork auszuführen. In diesem Fall wird Ihre Arbeit an einem Threadpool-Thread ausgeführt. –

Verwandte Themen