Verwenden Sie für asynchrone Vorgänge in ASP.NET MVC einen Thread aus ThreadPool unter .NET 4

158

Nach dieser Frage kann ich problemlos asynchrone Vorgänge in ASP.NET MVC verwenden. Also habe ich zwei Blog-Beiträge dazu geschrieben:

Ich habe zu viele Missverständnisse über asynchrone Vorgänge unter ASP.NET MVC.

Ich höre immer diesen Satz: Die Anwendung kann besser skaliert werden, wenn Vorgänge asynchron ausgeführt werden

Und ich habe diese Art von Sätzen auch oft gehört : Wenn Sie ein großes Verkehrsaufkommen haben, ist es möglicherweise besser, wenn Sie Ihre Abfragen nicht asynchron ausführen. Wenn Sie zwei zusätzliche Threads für die Bearbeitung einer Anfrage benötigen, werden Ressourcen von anderen eingehenden Anfragen abgezogen.

Ich denke, diese beiden Sätze sind inkonsistent.

Ich habe nicht viele Informationen darüber, wie Threadpool unter ASP.NET funktioniert, aber ich weiß, dass Threadpool eine begrenzte Größe für Threads hat. Der zweite Satz muss sich also auf dieses Thema beziehen.

Und ich möchte wissen, ob asynchrone Vorgänge in ASP.NET MVC einen Thread von ThreadPool unter .NET 4 verwenden.

Wie strukturiert die App beispielsweise, wenn wir einen AsyncController implementieren? Ist es eine gute Idee, AsyncController zu implementieren, wenn ich großen Datenverkehr bekomme?

Gibt es jemanden da draußen, der diesen schwarzen Vorhang vor meinen Augen wegnehmen und mir den Deal über Asynchronität unter ASP.NET MVC 3 (NET 4) erklären kann?

Bearbeiten:

Ich habe dieses Dokument fast hunderte Male gelesen und verstehe das Hauptgeschäft, aber ich habe immer noch Verwirrung, weil es zu viele inkonsistente Kommentare gibt.

Verwenden eines asynchronen Controllers in ASP.NET MVC

Bearbeiten:

Nehmen wir an, ich habe eine Controller-Aktion wie unten (keine Implementierung von AsyncController):

public ViewResult Index() { 

    Task.Factory.StartNew(() => { 
        //Do an advanced looging here which takes a while
    });

    return View();
}

Wie Sie hier sehen, feuere ich eine Operation ab und vergesse sie. Dann kehre ich sofort zurück, ohne darauf zu warten, dass es abgeschlossen ist.

Muss in diesem Fall ein Thread aus dem Threadpool verwendet werden? Wenn ja, was passiert nach Abschluss des Vorgangs mit diesem Thread? Kommt es GCrein und räumt es gleich nach Abschluss auf?

Bearbeiten:

Für die Antwort von @ Darin finden Sie hier ein Beispiel für asynchronen Code, der mit der Datenbank kommuniziert:

public class FooController : AsyncController {

    //EF 4.2 DbContext instance
    MyContext _context = new MyContext();

    public void IndexAsync() { 

        AsyncManager.OutstandingOperations.Increment(3);

        Task<IEnumerable<Foo>>.Factory.StartNew(() => { 

           return 
                _context.Foos;
        }).ContinueWith(t => {

            AsyncManager.Parameters["foos"] = t.Result;
            AsyncManager.OutstandingOperations.Decrement();
        });

        Task<IEnumerable<Bars>>.Factory.StartNew(() => { 

           return 
                _context.Bars;
        }).ContinueWith(t => {

            AsyncManager.Parameters["bars"] = t.Result;
            AsyncManager.OutstandingOperations.Decrement();
        });

        Task<IEnumerable<FooBar>>.Factory.StartNew(() => { 

           return 
                _context.FooBars;
        }).ContinueWith(t => {

            AsyncManager.Parameters["foobars"] = t.Result;
            AsyncManager.OutstandingOperations.Decrement();
        });
    }

    public ViewResult IndexCompleted(
        IEnumerable<Foo> foos, 
        IEnumerable<Bar> bars,
        IEnumerable<FooBar> foobars) {

        //Do the regular stuff and return

    }
}
Tugberk
quelle
Ich bin mir bei der Antwort nicht sicher, aber es ist erwähnenswert, dass Asynchron und Multithreading verschiedene Dinge sind. Es wäre also möglich, eine feste Anzahl von Threads mit asynchroner Behandlung zu haben. Was passieren würde, wäre, wenn eine Seite zum Beispiel E / A blockieren muss und eine andere Seite die Chance bekommt, auf demselben Thread ausgeführt zu werden. So können diese beiden Aussagen wahr sein, Async kann die Dinge schneller machen, aber zu viele Threads sind ein Problem.
Chris Chilvers
@ChrisChilvers Ja, Multithreading ist im asynchronen Betrieb nicht immer erforderlich. Ich habe das schon gedacht, aber ich glaube, ich habe keinen Controller darüber, soweit ich das beurteilen kann. AsyncController dreht, wie viele Threads es aus meiner Sicht will, ist sich aber auch nicht sicher. Gibt es eine Vorstellung von Threadpool auch auf Desktop-Apps wie WPF? Ich denke, die Anzahl der Threads ist bei solchen Apps kein Problem.
Tugberk
6
Schauen Sie sich das Video Threading mit Jeff Richter
oleksii
Ich denke, das Problem (und damit die Inkonsistenz) ist, dass die zweite Anweisung asynchron verwendet, wenn sie viele Threads bedeutet. Dies könnte daran liegen, dass asp.net auf diese Weise asynchrone Seiten implementiert hat und daher die spezifische Implementierung das Problem verwirrt hat (da der Name der Funktion, die das Problem verursacht, asynchrone Seiten sind), aber ich bin mir über die spezifische Implementierung nicht sicher . Entweder bedeuten sie "viele Threads" oder "asynchrone Seiten in asp.net Version X", da zukünftige Versionen die Implementierung ändern können. Oder sie bedeuten einfach, den Thread-Pool zu verwenden, um eine Asynchronisierung innerhalb einer Seite durchzuführen.
Chris Chilvers
@ ChrisChilvers oh Mann! Ich bin nach diesen Kommentaren verwirrter: s
Tugberk

Antworten:

177

Hier ist ein ausgezeichneter Artikel , den Sie lesen sollten, um die asynchrone Verarbeitung in ASP.NET besser zu verstehen (was asynchrone Controller im Grunde darstellen).

Betrachten wir zunächst eine standardmäßige synchrone Aktion:

public ActionResult Index()
{
    // some processing
    return View();
}

Wenn eine Anforderung an diese Aktion gestellt wird, wird ein Thread aus dem Thread-Pool gezogen und der Hauptteil dieser Aktion wird für diesen Thread ausgeführt. Wenn die Verarbeitung in dieser Aktion langsam ist, blockieren Sie diesen Thread für die gesamte Verarbeitung, sodass dieser Thread nicht zur Verarbeitung anderer Anforderungen wiederverwendet werden kann. Am Ende der Anforderungsausführung wird der Thread an den Thread-Pool zurückgegeben.

Nehmen wir nun ein Beispiel für das asynchrone Muster:

public void IndexAsync()
{
    // perform some processing
}

public ActionResult IndexCompleted(object result)
{
    return View();
}

Wenn eine Anforderung an die Index-Aktion gesendet wird, wird ein Thread aus dem Thread-Pool gezogen und der Hauptteil der IndexAsyncMethode ausgeführt. Sobald die Ausführung dieser Methode abgeschlossen ist, wird der Thread an den Thread-Pool zurückgegeben. AsyncManager.OutstandingOperationsWenn Sie dann unter Verwendung des Standards den Abschluss des asynchronen Vorgangs signalisieren, wird ein weiterer Thread aus dem Thread-Pool gezogen und der Hauptteil der IndexCompletedAktion darauf ausgeführt und das Ergebnis an den Client gerendert.

In diesem Muster sehen wir also, dass eine einzelne Client-HTTP-Anforderung von zwei verschiedenen Threads ausgeführt werden kann.

Nun geschieht der interessante Teil innerhalb der IndexAsyncMethode. Wenn Sie eine Blockierungsoperation darin haben, verschwenden Sie den gesamten Zweck der asynchronen Controller vollständig, weil Sie den Arbeitsthread blockieren (denken Sie daran, dass der Hauptteil dieser Aktion für einen aus dem Thread-Pool gezogenen Thread ausgeführt wird).

Wann können wir also die Vorteile von asynchronen Controllern nutzen, die Sie vielleicht fragen?

Meiner Meinung nach können wir am meisten davon profitieren, wenn wir E / A-intensive Vorgänge ausführen (z. B. Datenbank- und Netzwerkaufrufe an Remotedienste). Wenn Sie einen CPU-intensiven Betrieb haben, bringen asynchrone Aktionen keinen großen Nutzen.

Warum können wir also von E / A-intensiven Operationen profitieren? Weil wir E / A-Abschlussports verwenden könnten . IOCP sind äußerst leistungsfähig, da Sie während der Ausführung des gesamten Vorgangs keine Threads oder Ressourcen auf dem Server verbrauchen.

Wie arbeiten Sie?

Angenommen, wir möchten den Inhalt einer Remote-Webseite mithilfe der WebClient.DownloadStringAsync- Methode herunterladen . Sie rufen diese Methode auf, die einen IOCP im Betriebssystem registriert und sofort zurückgibt. Während der Verarbeitung der gesamten Anforderung werden auf Ihrem Server keine Threads verbraucht. Alles passiert auf dem Remote-Server. Dies kann viel Zeit in Anspruch nehmen, ist Ihnen aber egal, da Sie Ihre Arbeitsthreads nicht gefährden. Sobald eine Antwort empfangen wurde, wird der IOCP signalisiert, ein Thread wird aus dem Thread-Pool gezogen und der Rückruf wird für diesen Thread ausgeführt. Aber wie Sie sehen, haben wir während des gesamten Prozesses keine Threads monopolisiert.

Gleiches gilt für Methoden wie FileStream.BeginRead, SqlCommand.BeginExecute, ...

Was ist mit der Parallelisierung mehrerer Datenbankaufrufe? Angenommen, Sie hatten eine synchrone Controller-Aktion, bei der Sie 4 blockierende Datenbankaufrufe nacheinander ausgeführt haben. Es ist einfach zu berechnen, dass die Ausführung Ihrer Controller-Aktion ungefähr 800 ms dauert, wenn jeder Datenbankaufruf 200 ms dauert.

Wenn Sie diese Aufrufe nicht nacheinander ausführen müssen, würde eine Parallelisierung die Leistung verbessern?

Das ist die große Frage, die nicht einfach zu beantworten ist. Vielleicht ja vielleicht nein. Es hängt ganz davon ab, wie Sie diese Datenbankaufrufe implementieren. Wenn Sie wie zuvor beschrieben asynchrone Controller und E / A-Abschlussports verwenden, steigern Sie die Leistung dieser Controller-Aktion und anderer Aktionen, da Sie keine Worker-Threads monopolisieren.

Wenn Sie sie jedoch schlecht implementieren (mit einem blockierenden Datenbankaufruf, der für einen Thread aus dem Thread-Pool ausgeführt wird), reduzieren Sie die Gesamtausführungszeit dieser Aktion grundsätzlich auf ungefähr 200 ms, aber Sie hätten also 4 Worker-Threads verbraucht Möglicherweise hat sich die Leistung anderer Anforderungen verschlechtert, die aufgrund fehlender Threads im Pool zur Verarbeitung möglicherweise ausgehungert sind.

Es ist daher sehr schwierig. Wenn Sie sich nicht bereit fühlen, umfangreiche Tests für Ihre Anwendung durchzuführen, implementieren Sie keine asynchronen Controller, da Sie wahrscheinlich mehr Schaden als Nutzen anrichten. Implementieren Sie sie nur, wenn Sie einen Grund dafür haben: Sie haben beispielsweise festgestellt, dass Standardaktionen für synchrone Steuerungen einen Engpass für Ihre Anwendung darstellen (natürlich nach ausführlichen Belastungstests und Messungen).

Betrachten wir nun Ihr Beispiel:

public ViewResult Index() { 

    Task.Factory.StartNew(() => { 
        //Do an advanced looging here which takes a while
    });

    return View();
}

Wenn eine Anforderung für die Indexaktion empfangen wird, wird ein Thread aus dem Thread-Pool gezogen, um seinen Body auszuführen, aber sein Body plant nur eine neue Aufgabe mithilfe von TPL . Die Aktionsausführung endet also und der Thread wird an den Thread-Pool zurückgegeben. Abgesehen davon verwendet TPL Threads aus dem Thread-Pool, um ihre Verarbeitung durchzuführen. Selbst wenn der ursprüngliche Thread in den Thread-Pool zurückgegeben wurde, haben Sie einen anderen Thread aus diesem Pool gezeichnet, um den Hauptteil der Aufgabe auszuführen. Sie haben also 2 Threads aus Ihrem wertvollen Pool gefährdet.

Betrachten wir nun Folgendes:

public ViewResult Index() { 

    new Thread(() => { 
        //Do an advanced looging here which takes a while
    }).Start();

    return View();
}

In diesem Fall erzeugen wir manuell einen Thread. In diesem Fall kann die Ausführung des Hauptteils der Indexaktion etwas länger dauern (da das Laichen eines neuen Threads teurer ist als das Ziehen eines Threads aus einem vorhandenen Pool). Die Ausführung des erweiterten Protokollierungsvorgangs erfolgt jedoch für einen Thread, der nicht Teil des Pools ist. Wir gefährden also keine Threads aus dem Pool, die für die Bearbeitung anderer Anforderungen frei bleiben.

Darin Dimitrov
quelle
1
Wirklich detailliert, danke! Nehmen wir an, wir haben 4 asynchrone Aufgaben ( System.Threading.Task), die in der IndexAsyncMethode ausgeführt werden. Innerhalb dieser Operationen führen wir Datenbankaufrufe an einen Server durch. Alle sind also E / A-intensive Operationen, oder? Erstellen wir in diesem Fall 4 separate Threads (oder erhalten 4 separate Threads aus dem Thread-Pool)? Angenommen, ich habe eine Multi-Core-Maschine, die auch parallel ausgeführt wird, oder?
Tugberk
10
@tugberk, Datenbankaufrufe sind E / A-Vorgänge, aber alles hängt davon ab, wie Sie sie implementieren. Wenn Sie einen blockierenden Datenbankaufruf verwenden, z. B. SqlCommand.ExecuteReaderverschwenden Sie alles, da dies ein blockierender Aufruf ist. Sie blockieren den Thread, auf dem dieser Aufruf ausgeführt wird, und wenn dieser Thread zufällig ein Thread aus dem Pool ist, ist er sehr schlecht. Sie profitieren nur, wenn Sie E / A-Abschlussports verwenden : SqlCommand.BeginExecuteReader. Wenn Sie IOCP nicht verwenden, egal was Sie tun, verwenden Sie keine asynchronen Controller, da Sie der Gesamtleistung Ihrer Anwendung mehr Schaden zufügen als davon profitieren.
Darin Dimitrov
1
Nun, die meiste Zeit benutze ich zuerst EF-Code. Ich bin mir nicht sicher, ob es passt. Ich habe ein Beispiel erstellt, das zeigt, was ich im Allgemeinen mache. Ich habe die Frage aktualisiert. Können Sie einen Blick darauf werfen?
Tugberk
3
@tugberk, Sie führen sie parallel aus, sodass die Gesamtausführungszeit kürzer ist als wenn Sie sie nacheinander ausführen. Um sie auszuführen, verwenden Sie jedoch Arbeitsthreads. Nun, eigentlich ist EF faul. Wenn Sie dies tun, führen _context.FooSie tatsächlich nichts aus. Sie erstellen nur einen Ausdrucksbaum. Seien Sie dabei äußerst vorsichtig. Die Abfrageausführung wird nur verzögert, wenn Sie mit der Aufzählung über die Ergebnismenge beginnen. Und wenn dies in der Ansicht geschieht, kann dies katastrophal für die Leistung sein. Um eine EF-Abfrage eifrig auszuführen, hängen Sie .ToList()am Ende an.
Darin Dimitrov
2
@tugberk, Sie benötigen ein Lasttest-Tool, um mehrere Benutzer parallel auf Ihrer Website zu simulieren und zu sehen, wie es sich unter hoher Last verhält. Mini Profiler kann das Laden Ihrer Website nicht simulieren. Dies kann Ihnen helfen, Ihre ADO.NET-Abfragen anzuzeigen und zu optimieren und eine einzelne Anforderung zu profilieren. Dies ist nutzlos, wenn Sie sehen möchten, wie sich Ihre Site in einer realen Situation verhält, wenn viele Benutzer darauf zugreifen.
Darin Dimitrov
49

Ja - alle Threads stammen aus dem Thread-Pool. Ihre MVC-App ist bereits Multithread-fähig. Wenn eine Anfrage eingeht, wird ein neuer Thread aus dem Pool entnommen und zur Bearbeitung der Anfrage verwendet. Dieser Thread wird (von anderen Anforderungen) "gesperrt", bis die Anforderung vollständig bearbeitet und abgeschlossen ist. Wenn im Pool kein Thread verfügbar ist, muss die Anforderung warten, bis einer verfügbar ist.

Wenn Sie über asynchrone Controller verfügen, erhalten diese immer noch einen Thread aus dem Pool, aber während sie die Anforderung bearbeiten, können sie den Thread aufgeben, während sie darauf warten, dass etwas passiert (und dieser Thread kann an eine andere Anforderung übergeben werden) und wenn die ursprüngliche Anforderung einen Thread benötigt wieder bekommt es einen aus dem pool.

Der Unterschied besteht darin, dass Ihnen bei vielen lang laufenden Anforderungen (bei denen der Thread auf eine Antwort von etwas wartet) möglicherweise die Threads aus dem Pool ausgehen, um selbst grundlegende Anforderungen zu bearbeiten. Wenn Sie über asynchrone Controller verfügen, haben Sie keine weiteren Threads, aber die wartenden Threads werden an den Pool zurückgegeben und können andere Anforderungen bearbeiten.

Ein fast richtiges Beispiel ... Denken Sie daran , wie auf einen Bus bekommen, gibt es fünf Menschen warten auf bekommen, die erste wird auf zahlt und setzt sich (der Fahrer ihre Anforderung bedient), Sie erhalten (der Fahrer bedient Ihre Anfrage), aber Sie können Ihr Geld nicht finden; Wenn Sie in Ihren Taschen herumfummeln, gibt der Fahrer Sie auf und setzt die nächsten zwei Personen ein (um ihre Anfragen zu bearbeiten). Wenn Sie Ihr Geld finden, beginnt der Fahrer erneut, sich um Sie zu kümmern (um Ihre Anfrage zu erfüllen) - die fünfte Person muss warten, bis Sie sind fertig, aber die dritte und vierte Person wurden bedient, während Sie auf halbem Weg waren. Dies bedeutet, dass der Fahrer der einzige Thread aus dem Pool ist und die Passagiere die Anforderungen sind. Es war zu kompliziert zu schreiben, wie es funktionieren würde, wenn es zwei Treiber gäbe, aber Sie können sich vorstellen ...

Ohne einen asynchronen Controller müssten die Passagiere hinter Ihnen ewig warten, während Sie nach Ihrem Geld suchen, während der Busfahrer keine Arbeit erledigen würde.

Die Schlussfolgerung lautet also: Wenn viele Leute nicht wissen, wo sich ihr Geld befindet (dh eine lange Zeit benötigen, um auf etwas zu antworten, das der Fahrer gefragt hat), könnten asynchrone Controller den Durchsatz von Anforderungen unterstützen und den Prozess von einigen beschleunigen. Ohne einen Aysnc-Controller warten alle, bis die Person vor ihnen vollständig behandelt wurde. ABER vergessen Sie nicht, dass Sie in MVC viele Bustreiber auf einem einzigen Bus haben, so dass Async keine automatische Wahl ist.

K. Bob
quelle
8
Sehr schöne Analogie. Danke dir.
Pittsburgh DBA
Mir hat die Beschreibung gefallen. Vielen Dank
Omer Cansizoglu
Ausgezeichnete Art, es zu erklären. Danke,
Anand Vyas
Ihre Antwort in Kombination mit Darins Antwort fasst den gesamten Mechanismus hinter asynchronen Controllern zusammen, was es ist und was noch wichtiger ist, was es nicht ist!
Nirman
Dies ist eine nette Analogie, aber meine einzige Frage ist folgende: Der Typ, der in Ihrer Analogie in seinen Taschen herumfummelt, wäre eine Art Arbeit / Verarbeitung in unserer Anwendung ... also welche Prozesse funktionieren, wenn wir den Thread freigeben? Sicher ist es ein anderer Thread? Was gewinnen wir hier?
Tomuke
10

Hier spielen zwei Konzepte eine Rolle. Zunächst können wir unseren Code parallel ausführen lassen, um ihn schneller auszuführen, oder Code in einem anderen Thread planen, um zu vermeiden, dass der Benutzer wartet. Das Beispiel, das Sie hatten

public ViewResult Index() { 

    Task.Factory.StartNew(() => { 
        //Do an advanced looging here which takes a while
    });

    return View();
}

gehört zur zweiten Kategorie. Der Benutzer erhält eine schnellere Antwort, aber die Gesamtauslastung des Servers ist höher, da er die gleiche Arbeit erledigen und das Threading übernehmen muss.

Ein weiteres Beispiel hierfür wäre:

public ViewResult Index() { 

    Task.Factory.StartNew(() => { 
        //Make async web request to twitter with WebClient.DownloadString()
    });

    Task.Factory.StartNew(() => { 
        //Make async web request to facebook with WebClient.DownloadString()
    });


    //wait for both to be ready and merge the results

    return View();
}

Da die Anforderungen parallel ausgeführt werden, muss der Benutzer nicht so lange warten, als ob sie seriell ausgeführt würden. Sie sollten sich jedoch darüber im Klaren sein, dass wir hier mehr Ressourcen verbrauchen, als wenn wir seriell ausgeführt würden, da wir den Code in vielen Threads ausführen, während auch ein Thread wartet.

Dies ist in einem Client-Szenario vollkommen in Ordnung. Und es ist dort durchaus üblich, synchronen Code mit langer Laufzeit in eine neue Aufgabe zu verpacken (auf einem anderen Thread auszuführen), um die Benutzeroberfläche ansprechbar zu halten oder zu parallisieren, um sie schneller zu machen. Ein Thread wird jedoch weiterhin für die gesamte Dauer verwendet. Auf einem Server mit hoher Auslastung kann dies nach hinten losgehen, da Sie tatsächlich mehr Ressourcen verwenden. Darum haben dich die Leute gewarnt

Async-Controller in MVC haben jedoch ein anderes Ziel. Hier geht es darum zu vermeiden, dass Threads herum sitzen und nichts tun (was die Skalierbarkeit beeinträchtigen kann). Es ist wirklich nur wichtig, ob die APIs, die Sie aufrufen, über asynchrone Methoden verfügen. Wie WebClient.DowloadStringAsync ().

Der Punkt ist, dass Sie Ihren Thread zurückgeben können, um neue Anforderungen zu verarbeiten, bis die Webanforderung abgeschlossen ist, wo Sie einen Rückruf aufrufen, der denselben oder einen neuen Thread erhält, und die Anforderung beenden.

Ich hoffe, Sie verstehen den Unterschied zwischen asynchron und parallel. Stellen Sie sich parallelen Code als Code vor, in dem sich Ihr Thread befindet, und warten Sie auf das Ergebnis. Während asynchroner Code Code ist, bei dem Sie benachrichtigt werden, wenn der Code fertig ist und Sie wieder daran arbeiten können, kann der Thread in der Zwischenzeit andere Arbeiten ausführen.

Mikael Eliasson
quelle
6

Anwendungen können besser skaliert werden, wenn Vorgänge asynchron ausgeführt werden, jedoch nur, wenn Ressourcen für die Wartung der zusätzlichen Vorgänge verfügbar sind .

Asynchrone Vorgänge stellen sicher, dass Sie eine Aktion niemals blockieren, da eine vorhandene ausgeführt wird. ASP.NET verfügt über ein asynchrones Modell, mit dem mehrere Anforderungen nebeneinander ausgeführt werden können. Es wäre möglich, die Anforderungen in die Warteschlange zu stellen und sie im FIFO zu verarbeiten. Dies lässt sich jedoch nicht gut skalieren, wenn Hunderte von Anforderungen in die Warteschlange gestellt werden und die Verarbeitung jeder Anforderung 100 ms dauert.

Wenn Sie ein großes Verkehrsaufkommen haben, ist es möglicherweise besser, Ihre Abfragen nicht asynchron auszuführen, da möglicherweise keine zusätzlichen Ressourcen für die Bearbeitung der Anforderungen vorhanden sind . Wenn keine freien Ressourcen vorhanden sind, müssen Ihre Anforderungen in die Warteschlange gestellt werden, exponentiell länger dauern oder sofort fehlschlagen. In diesem Fall bietet Ihnen der asynchrone Overhead (Mutexe und Kontextwechselvorgänge) nichts.

Was ASP.NET betrifft, haben Sie keine Wahl - es wird ein asynchrones Modell verwendet, da dies für das Server-Client-Modell sinnvoll ist. Wenn Sie intern Ihren eigenen Code schreiben, der ein asynchrones Muster verwendet, um eine bessere Skalierung zu versuchen, werden Sie keine Verbesserungen feststellen, es sei denn, Sie versuchen, eine Ressource zu verwalten, die von allen Anforderungen gemeinsam genutzt wird, da diese bereits verpackt sind in einem asynchronen Prozess, der nichts anderes blockiert.

Letztendlich ist alles subjektiv, bis Sie tatsächlich sehen, was einen Engpass in Ihrem System verursacht. Manchmal ist es offensichtlich, wo ein asynchrones Muster hilft (indem verhindert wird, dass Ressourcen in der Warteschlange blockiert werden). Letztendlich kann nur das Messen und Analysieren eines Systems anzeigen, wo Sie Effizienzgewinne erzielen können.

Bearbeiten:

In Ihrem Beispiel stellt der Task.Factory.StartNewAufruf eine Operation im .NET-Thread-Pool in die Warteschlange. Die Art der Thread-Pool-Threads muss wiederverwendet werden (um die Kosten für das Erstellen / Zerstören vieler Threads zu vermeiden). Sobald der Vorgang abgeschlossen ist, wird der Thread wieder in den Pool freigegeben, um von einer anderen Anforderung wiederverwendet zu werden (der Garbage Collector wird erst dann beteiligt, wenn Sie einige Objekte in Ihren Vorgängen erstellt haben. In diesem Fall werden sie wie gewohnt erfasst Umfang).

Für ASP.NET gibt es hier keine spezielle Operation. Die ASP.NET-Anforderung wird ohne Berücksichtigung der asynchronen Aufgabe abgeschlossen. Die einzige Sorge könnte sein, dass Ihr Thread-Pool überlastet ist (dh, dass derzeit keine Threads verfügbar sind, um die Anforderung zu bearbeiten, und die Einstellungen des Pools nicht zulassen, dass weitere Threads erstellt werden). In diesem Fall wird die Anforderung blockiert und wartet auf den Start der Aufgabe, bis ein Pool-Thread verfügbar wird.

Paul Turner
quelle
Vielen Dank! Nachdem ich Ihre Antwort gelesen habe, habe ich die Frage mit einem Codebeispiel bearbeitet. Kannst du einen Blick darauf werfen?
Tugberk
Sie haben dort einen magischen Satz für mich: Mit Task.Factory.StartNewcall wird eine Operation im .NET-Thread-Pool in die Warteschlange gestellt. . In diesem Zusammenhang, welcher hier richtig ist: 1-) Es wird ein neuer Thread erstellt, und wenn dies erledigt ist, kehrt dieser Thread zum Threadpool zurück und wartet dort, bis er wieder verwendet wird. 2-) Es wird ein Thread aus dem Threadpool abgerufen und dieser Thread kehrt zum Threadpool zurück und wartet dort, bis er wieder verwendet wird.3-) Es ist der effizienteste Ansatz und kann beides tun.
Tugberk
1
Der Thread-Pool erstellt Threads nach Bedarf und recycelt Threads, wenn sie nicht verwendet werden. Das genaue Verhalten ist in den CLR-Versionen unterschiedlich. Spezifische Informationen dazu finden Sie hier msdn.microsoft.com/en-us/library/0ka9477y.aspx
Paul Turner
Es beginnt sich jetzt in meinem Kopf zu formen. CLR besitzt also den Thread-Pool, oder? Zum Beispiel hat eine WPF-Anwendung auch den Begriff Thread-Pool und befasst sich auch mit dem dortigen Pool.
Tugberk
1
Der Thread-Pool ist eine eigene Sache innerhalb der CLR. Andere Komponenten, die den Pool "kennen", geben an, dass sie Thread-Pool-Threads (falls zutreffend) verwenden, anstatt ihre eigenen zu erstellen und zu zerstören. Das Erstellen oder Zerstören eines Threads ist eine relativ teure Operation, daher ist die Verwendung des Pools ein großer Effizienzgewinn bei kurzfristigen Operationen.
Paul Turner
2

Ja, sie verwenden einen Thread aus dem Thread-Pool. Es gibt tatsächlich einen ziemlich ausgezeichneten Leitfaden von MSDN, der alle Ihre Fragen und mehr behandelt. Ich habe es in der Vergangenheit als sehr nützlich empfunden. Hör zu!

http://msdn.microsoft.com/en-us/library/ee728598.aspx

In der Zwischenzeit sollten die Kommentare + Vorschläge, die Sie über asynchronen Code hören, mit einem Körnchen Salz aufgenommen werden. Wenn Sie etwas asynchron machen, wird die Skalierung nicht unbedingt verbessert, und in einigen Fällen kann sich die Skalierung Ihrer Anwendung verschlechtern. Der andere Kommentar, den Sie zu "einem riesigen Verkehrsaufkommen ..." gepostet haben, ist ebenfalls nur in bestimmten Kontexten korrekt. Es hängt wirklich davon ab, was Ihre Vorgänge tun und wie sie mit anderen Teilen des Systems interagieren.

Kurz gesagt, viele Leute haben viele Meinungen über Async, aber sie sind möglicherweise nicht kontextbezogen korrekt. Ich würde sagen, konzentrieren Sie sich auf Ihre genauen Probleme und führen Sie grundlegende Leistungstests durch, um festzustellen, mit welchen asynchronen Controllern usw. tatsächlich umgegangen wird Ihrer Anwendung .

AR
quelle
Ich habe dieses Dokument vielleicht hunderte Male gelesen und bin immer noch so verwirrt (vielleicht bin ich das Problem, wer weiß). Wenn Sie sich umschauen, sehen Sie so viele inkonsistente Kommentare zur Asynchronität in ASP.NET MVC, wie Sie in meiner Frage sehen können.
Tugberk
für den letzten Satz: Innerhalb einer Controller-Aktion habe ich eine Datenbank 5 Mal separat abgefragt (ich musste) und alles dauerte ungefähr 400 ms. Dann habe ich AsyncController implementiert und parallel ausgeführt. Die Reaktionszeit reduzierte sich dramatisch auf ca. 200 ms. Aber ich habe keine Ahnung, wie viele Threads erstellt werden, was mit diesen Threads passiert, nachdem ich mit ihnen fertig bin, GCkommt und bereinigt sie direkt, nachdem ich fertig bin, damit meine App keinen Speicherverlust aufweist, und so weiter. Irgendeine Idee dazu.
Tugberk
Fügen Sie einen Debugger hinzu und finden Sie es heraus.
AR
0

Als erstes ist es nicht MVC, sondern der IIS, der den Thread-Pool verwaltet. Daher wird jede Anforderung, die an eine MVC- oder ASP.NET-Anwendung geht, von Threads bedient, die im Thread-Pool verwaltet werden. Erst wenn er die App Asynch macht, ruft er diese Aktion in einem anderen Thread auf und gibt den Thread sofort frei, damit andere Anforderungen entgegengenommen werden können.

Ich habe dasselbe mit einem Detailvideo ( http://www.youtube.com/watch?v=wvg13n5V0V0/ "MVC Asynch-Controller und Thread-Hunger") erklärt, das zeigt, wie Thread-Hunger in MVC auftritt und wie er durch die Verwendung von MVC minimiert wird Asynch-Controller. Ich habe auch die Anforderungswarteschlangen mit perfmon gemessen, damit Sie sehen können, wie die Anforderungswarteschlangen für MVC-Asynchrone verringert werden und wie schlecht sie für Synchronisierungsvorgänge sind.

Shivprasad Koirala
quelle