2016-08-11 12 views
0

Ich bin gerade auf diesen Code gestoßen. Ich begann sofort zu krümmen und redete mit mir selbst (nicht nette Dinge). Die Sache ist, ich verstehe nicht wirklich warum und kann sie nicht vernünftig artikulieren. Es sieht einfach sehr schlecht für mich aus - vielleicht liege ich falsch.Verwenden von ThreadPool und Task.Wait innerhalb einer Async/Await-Methode

public async Task<IHttpActionResult> ProcessAsync() 
{ 
    var userName = Username.LogonName(User.Identity.Name);  
    var user = await _user.GetUserAsync(userName); 

    ThreadPool.QueueUserWorkItem((arg) => 
     { 
      Task.Run(() => _billing.ProcessAsync(user)).Wait(); 
     });  
    return Ok(); 
} 

Dieser Code sieht für mich wie es unnötig Fäden mit ThreadPool.QueueUserWorkItem und Task.Run zu schaffen ist. Außerdem sieht es so aus, als könnte es zu Deadlocks oder ernsthaften Ressourcenproblemen unter schwerer Last kommen. Hab ich recht?

Die _billing.ProcessAsync() -Methode ist erwartbar (async), so würde ich erwarten, dass ein einfaches "erwarten" Schlüsselwort wäre das Richtige zu tun und nicht all das andere Gepäck.

+4

Ja, es ist sehr schlecht. Auch dies sieht aus wie Code, der in einer Web-Seite läuft, likey taten sie 'ThreadPool.QueueUserWorkItem' und eigentlich vorhatte [' HostingEnvironment.QueueBackgroundWorkItem'] (https://msdn.microsoft.com/en-us/library/dn636893 (v = vs.110) .aspx) was Sie tun sollten, wenn Sie Hintergrundthreads auf IIS haben. –

Antwort

3

Es ist seltsam, aber je nachdem, was der Autor versucht, zu erreichen, scheint es in Ordnung ein Arbeitselement im Thread-Pool aus einer asynchronen Methode queue.

Dies ist nicht wie ein Thread zu starten, es ist nur Schlange eine Aktion in einem ThreadPool Thread getan werden, wenn es eine freie ist. Daher kann die asynchrone Methode (ProcessAsync) fortgesetzt werden und muss sich nicht um das Ergebnis kümmern.

Das Seltsame ist der Code innerhalb des Lambda, der im ThreadPool eingereiht wird. Nicht nur die Task.Run() (die überflüssig ist und nur unnötigen Overhead verursacht), sondern eine async Methode ohne warten für sie zu beenden ist in einer Methode, die von der ThreadPool ausgeführt werden sollte, schlecht, weil es den Steuerungsfluss an die zurückgegeben wird Anrufer wenn await ing etwas.

Also denkt der ThreadPool schließlich, dass diese Methode beendet ist (und der Thread für die nächste Aktion in der Warteschlange frei ist), während die Methode eigentlich später wieder aufgenommen werden soll.

Dies kann zu sehr undefined Verhalten führen. Dieser Code kann funktionieren (unter bestimmten Umständen), aber ich würde nicht auf darauf verlassen und es als produktiven Code verwenden.

(Gleiches gilt für das Aufrufen einer nicht erwarteten asynchronen Methode in Task.Run(), da die Task "denkt", dass sie beendet ist, während die Methode später tatsächlich fortgesetzt werden soll).


Als Lösung, die ich einfach await dass async Methode vorschlagen würde, auch:

await _billing.ProcessAsync(user); 

Aber natürlich ohne Wissen über den Kontext der Code-Snippets ich nichts garantieren kann. Hinweis, dass dies das Verhalten ändern würde: Während der Code bisher nicht auf _billing.ProcessAsync() auf Finih wartete, würde es jetzt tun. So verlassen vielleicht await und nur Feuer und vergessen

_billing.ProcessAsync(user); 

vielleicht gut genug, auch.

+0

Sehr gute Antwort. Ich denke, dass ein einfaches "Warten" das empfohlene Heilmittel sein wird. Sind Sie einverstanden? –

+0

@BigDaddy ja die Schlussfolgerung vergessen ... soweit ich sehen kann "erwarten" wäre richtig. –

+0

Wenn die in QueueUserWorkItem eingepackte Aufgabe eine lang andauernde Operation ist, ändert sich dadurch irgendetwas? –

6

Ich glaube, Scott ist richtig mit seiner Annahme, dass ThreadPool.QueueUserWorkItemHostingEnvironment.QueueBackgroundWorkItem hätte sein sollen. Der Aufruf von Task.Run und Wait ist jedoch völlig unsinnig - sie arbeiten mit dem Thread-Pool sind drängen und auf einen Thread-Pool-Thread zu blockieren, wenn der Code bereits auf dem Thread-Pool ist.

Die _billing.ProcessAsync() -Methode ist zu erwarten (async), so würde ich erwarten, dass ein einfaches "warten" -Schlüsselwort das Richtige wäre und nicht all das andere Gepäck.

Ich stimme stark.

Allerdings, dies wird das Verhalten der Aktion ändern. Es wird jetzt gewartet, bis Billing.ProcessAsync abgeschlossen ist, während vorher es früh zurückkommen würde. Beachten Sie, dass die frühzeitige Rückkehr zu ASP.NET fast immer ein Fehler ist - ich würde sagen, dass jede "Rechnungsverarbeitung" noch sicherer wäre. Wenn Sie also dieses Chaos mit await ersetzen, wird die App korrekter, aber die Aktion ProcessAsync dauert länger, um zum Client zurückzukehren.

+0

Danke, aber ich bin nur ein wenig verwirrt. Die Aufgabe, die erstellt wird, verwendet "Warten", aber Sie sagen, dass es früh zurückkehrt. Was ist "Wait" in diesem Kontext? Ist es ähnlich wie "Ergebnis"? –

+0

@BigDaddy: 'Wait' blockiert nur einen Thread-Thread, bis die Aufgabe abgeschlossen ist. In diesem Fall macht es nichts anderes als einen Thread-Pool-Thread zu verwenden - Sie können das 'Wait' (und das' Task.Run') vollständig loswerden und der Code würde sich genau gleich verhalten (außer dass weniger Ressourcen verwendet werden) . 'Wait' ist sehr ähnlich zu 'Result', da beide den aufrufenden Thread blockieren, bis die Aufgabe abgeschlossen ist. Die Aktion wird vorzeitig beendet, da sie nicht auf die von QUWI eingereihte Arbeit wartet. –

+0

Wenn die in QueueUserWorkItem eingepackte Aufgabe eine lang andauernde Operation ist, ändert sich dadurch irgendetwas? –

Verwandte Themen