Kann ich Threads verwenden, um lang laufende Jobs auf IIS auszuführen?

85

In einer ASP.Net-Anwendung klickt der Benutzer auf eine Schaltfläche auf der Webseite. Dadurch wird ein Objekt auf dem Server über den Ereignishandler instanziiert und eine Methode für das Objekt aufgerufen. Die Methode geht zu einem externen System, um Dinge zu erledigen, und dies kann eine Weile dauern. Ich möchte diesen Methodenaufruf also in einem anderen Thread ausführen, damit ich dem Benutzer mit "Ihre Anfrage wurde gesendet" die Kontrolle zurückgeben kann. Ich bin einigermaßen glücklich, dies als Feuer und Vergessen zu tun, obwohl es noch schöner wäre, wenn der Benutzer das Objekt weiterhin nach dem Status abfragen könnte.

Was ich nicht weiß, ist, ob IIS zulässt, dass mein Thread weiter ausgeführt wird, selbst wenn die Benutzersitzung abläuft. Stellen Sie sich vor, der Benutzer löst das Ereignis aus und wir instanziieren das Objekt auf dem Server und lösen die Methode in einem neuen Thread aus. Der Benutzer ist mit der Meldung "Ihre Anfrage wurde gesendet" zufrieden und schließt seinen Browser. Letztendlich läuft diese Benutzersitzung auf IIS ab, aber der Thread wird möglicherweise noch ausgeführt und erledigt die Arbeit. Erlaubt IIS, dass der Thread weiter ausgeführt wird, oder beendet er ihn und entsorgt das Objekt, sobald die Benutzersitzung abläuft?

BEARBEITEN: Aus den Antworten und Kommentaren geht hervor, dass der beste Weg, dies zu tun, darin besteht, die lang laufende Verarbeitung außerhalb von IIS zu verschieben. Dies betrifft neben allem anderen das Problem des Appdomain-Recyclings. In der Praxis muss ich Version 1 in begrenzter Zeit auf den Markt bringen und innerhalb eines vorhandenen Frameworks arbeiten. Daher möchte ich die Service-Schicht vermeiden, daher der Wunsch, den Thread in IIS einfach abzufeuern. In der Praxis dauert "lange laufen" hier nur wenige Minuten und die Parallelität auf der Website ist gering, daher sollte es in Ordnung sein. Die nächste Version muss jedoch definitiv in eine separate Service-Schicht aufgeteilt werden.

Frans
quelle
1
Haben Sie weitere Informationen darüber, was verarbeitet werden muss und welche Hosting-Umgebung Sie haben? Können Sie beispielsweise einen Dienst installieren?
Senfo
Sie können jederzeit die Programmiermethoden Async und Await (Visual Studio 2012+) verwenden. Viel sauberer als das Verwalten von Threads selbst.
SausageFingers

Antworten:

65

Sie können erreichen, was Sie wollen, aber es ist normalerweise eine schlechte Idee. Mehrere ASP.NET-Blog- und CMS-Engines verfolgen diesen Ansatz, da sie auf einem gemeinsam genutzten Hosting-System installierbar sein möchten und nicht von einem Windows-Dienst abhängig sind, der installiert werden muss. Normalerweise starten sie beim Starten der App einen lang laufenden Thread in Global.asax und lassen diesen Thread-Prozess Aufgaben in die Warteschlange stellen.

Neben der Reduzierung der Ressourcen, die IIS / ASP.NET für die Verarbeitung von Anforderungen zur Verfügung stehen, treten auch Probleme mit dem Thread auf, der beim Recycling der AppDomain beendet wird. Anschließend müssen Sie sich mit der Persistenz der Aufgabe während des Flugs befassen Sie können die Arbeit auch wieder starten, wenn die AppDomain wieder hochgefahren wird.

Beachten Sie, dass die AppDomain in vielen Fällen automatisch in einem Standardintervall wiederverwendet wird sowie wenn Sie die web.config usw. aktualisieren.

Wenn Sie die Persistenz- und Transaktionsaspekte Ihres Threads, der jederzeit beendet wird, bewältigen können, können Sie das AppDomain-Recycling umgehen, indem Sie einen externen Prozess durchführen, der in bestimmten Abständen eine Anforderung an Ihre Site stellt. Wenn die Site also recycelt wird, können Sie dies tun Es wird garantiert, dass es innerhalb von X Minuten automatisch wieder gestartet wird.

Auch dies ist normalerweise eine schlechte Idee.

EDIT: Hier sind einige Beispiele dieser Technik in Aktion:

Community Server: Verwenden von Windows-Diensten im Vergleich zum Hintergrundthread zum Ausführen von Code in geplanten Intervallen Erstellen eines Hintergrundthreads beim ersten Start der Website

BEARBEITEN (aus der fernen Zukunft) - In diesen Tagen würde ich Hangfire verwenden .

Bryan Batchelder
quelle
1
Danke, das ist aufschlussreich. Das Recycling ist ein absoluter Killer und ich nehme von dem, was Sie sagen , dass ich kann dies als Hard-und-schnell , was tun , aber es ist gefährlich.
Frans
Ja, hier überprüfen Sie dies: professionalaspnet.com/archive/2007/09/02/… Und hier ist ein Thread darüber, wie COmmunity Server dasselbe behandelt: dev.communityserver.com/forums/t/459942.aspx
Bryan Batchelder
In meinem Fall konnte ich IIS nur anweisen, niemals zu recyceln und die Site niemals im Leerlauf zu betreiben. Dies führte dazu, dass ich unter dieser Annahme arbeiten konnte und die Site nur während der Bereitstellung und bei geplanten Wartungsarbeiten recycelt werden konnte. Da ich gerade eine Admin-Site erstellt habe, hat dies für mich perfekt funktioniert.
Brett
1
Seltsam, dass dies so viele positive Stimmen hat. Dass dies möglich ist, ist eines der sehr coolen Dinge bei ASP.NET: Alle Anforderungen werden in derselben AppDomain zusammen mit allen Hintergrundthreads, die Sie erstellen können, bereitgestellt. Keiner der angegebenen Gründe, warum dies eine "schlechte Idee" ist, scheint mir überzeugend. AppDomains können ausfallen, ja - aber das ist in der ASP.NET-Umgebung kaum einzigartig. Sie müssen gut mit Ihrer Hosting-Umgebung spielen (google for HostingEnvironment.RegisterObject).
John
3
.NET 4.5.2 hat eine neue Funktion hinzugefügt, mit der QueueBackgroundWorkItemHintergrundaufgaben einfach registriert werden können. Möglicherweise möchten Sie Ihre Antwort erneut aktualisieren.
Scott Chamberlain
38

Ich bin mit der akzeptierten Antwort nicht einverstanden.

Verwenden eines Hintergrundthreads (oder einer Aufgabe, mit der begonnen wurde) Task.Factory.StartNew ) ist in ASP.NET in Ordnung. Wie in allen Hosting-Umgebungen möchten Sie möglicherweise die Funktionen zum Herunterfahren verstehen und mit ihnen zusammenarbeiten.

In ASP.NET können Sie mithilfe der HostingEnvironment.RegisterObjectMethode Arbeiten registrieren, die beim Herunterfahren ordnungsgemäß gestoppt werden müssen. Siehe diesen Artikel und die Kommentare für eine Diskussion.

(Wie Gerard in seinem Kommentar betont, gibt es jetzt auch das HostingEnvironment.QueueBackgroundWorkItem, worauf es ankommtRegisterObject einen Scheduler für das Hintergrundelement zu registrieren, an dem gearbeitet werden soll. Insgesamt ist die neue Methode besser, da sie aufgabenbasiert ist.)

Was das allgemeine Thema betrifft, von dem Sie oft hören, dass es eine schlechte Idee ist, ziehen Sie die Alternative in Betracht, einen Windows-Dienst (oder eine andere Art von Extra-Prozess-Anwendung) bereitzustellen:

  • Keine triviale Bereitstellung mehr mit Webbereitstellung
  • Nicht nur auf Azure-Websites bereitstellbar
  • Abhängig von der Art der Hintergrundaufgabe müssen die Prozesse wahrscheinlich kommunizieren. Das bedeutet, dass entweder irgendeine Form von IPC oder der Dienst auf eine gemeinsame Datenbank zugreifen muss.

Beachten Sie auch, dass in einigen erweiterten Szenarien der Hintergrund-Thread möglicherweise sogar im selben Adressraum wie die Anforderungen ausgeführt werden muss. Ich sehe die Tatsache, dass ASP.NET dies tun kann, als einen großen Vorteil, der durch .NET möglich geworden ist.

John
quelle
Das ist toll. Ich habe eine Aufgabe, die ungefähr 30 Sekunden dauert. Perfekt, wenn Sie den App-Pool nur so lange verzögern müssen, bis die Arbeit erledigt ist.
Scuba Steve
1
Ab .Net Framework 4.5.2 können Sie auch HostingEnvironment.QueueBackgroundWorkItem (Aktion <CancellationToken>) verwenden
Gerard Carbó
In meinen Beobachtungen scheint es, dass IIS die von mir ausgeführte ASP.NET-Web-API schließlich herunterfährt und sie erst wieder startet, wenn eine neue Anforderung eingeht. Zum festgelegten Zeitpunkt, zu dem mein Thread die Anwendung starten soll nicht einmal laufen. Liege ich falsch?
David Sopko
7

Sie möchten für diese Aufgabe keinen Thread aus dem IIS-Thread-Pool verwenden, da dieser Thread künftige Anforderungen nicht mehr verarbeiten kann. Sie könnten sich in ASP.NET 2.0 mit asynchronen Seiten befassen , aber das wäre auch nicht die richtige Antwort. Stattdessen klingt es so, als würden Sie von Microsoft Message Queuing profitieren . Im Wesentlichen würden Sie die Aufgabendetails zur Warteschlange hinzufügen, und ein anderer Hintergrundprozess (möglicherweise ein Windows-Dienst) wäre für die Ausführung dieser Aufgabe verantwortlich. Unter dem Strich ist der Hintergrundprozess jedoch vollständig von IIS isoliert.

Senfo
quelle
Ah, MSMQ - eine meiner langfristigen Lieblingstechnologien. Danke für den Einblick und den Link.
Frans
6

Ich würde vorschlagen, HangFire für solche Anforderungen zu verwenden. Es ist ein schönes Feuer und Vergessen Engine läuft im Hintergrund, unterstützt verschiedene Architekturen, zuverlässig, weil es durch Persistenzspeicher unterstützt wird.

Vipul
quelle
5

Hier gibt es einen guten Thread und Beispielcode: http://forums.asp.net/t/1534903.aspx?PageIndex=2

Ich habe sogar mit der Idee gespielt, eine Keep-Alive-Seite auf meiner Website aus dem Thread heraus aufzurufen, um den App-Pool am Leben zu erhalten. Denken Sie daran, wenn Sie diese Methode verwenden, dass Sie eine wirklich gute Wiederherstellungsbehandlung benötigen, da die Anwendung jederzeit recycelt werden kann. Wie viele bereits erwähnt haben, ist dies nicht der richtige Ansatz, wenn Sie Zugriff auf andere Serviceoptionen haben. Für Shared Hosting ist dies jedoch möglicherweise eine Ihrer einzigen Optionen.

Um den App-Pool am Leben zu erhalten, können Sie während der Verarbeitung des Threads eine Anfrage an Ihre eigene Site senden. Dies kann dazu beitragen, den App-Pool am Leben zu erhalten, wenn Ihr Prozess längere Zeit ausgeführt wird.

string tempStr = GetUrlPageSource("http://www.mysite.com/keepalive.aspx");


    public static string GetUrlPageSource(string url)
    {
        string returnString = "";

        try
        {
            Uri uri = new Uri(url);
            if (uri.Scheme == Uri.UriSchemeHttp)
            {
                HttpWebRequest req = (HttpWebRequest)WebRequest.Create(uri);
                CookieContainer cookieJar = new CookieContainer();

                req.CookieContainer = cookieJar;

                //set the request timeout to 60 seconds
                req.Timeout = 60000;
                req.UserAgent = "MyAgent";

                //we do not want to request a persistent connection
                req.KeepAlive = false;

                HttpWebResponse resp = (HttpWebResponse)req.GetResponse();
                Stream stream = resp.GetResponseStream();
                StreamReader sr = new StreamReader(stream);

                returnString = sr.ReadToEnd();

                sr.Close();
                stream.Close();
                resp.Close();
            }
        }
        catch
        {
            returnString = "";
        }

        return returnString;
    }
Daniel
quelle
5

Wir haben diesen Weg eingeschlagen und es hat tatsächlich funktioniert, als sich unsere App auf einem Server befand. Wenn wir auf mehrere Computer skalieren wollten (oder mehrere w3wp in einem Web-Garen verwenden wollten), mussten wir neu bewerten und untersuchen, wie eine Arbeitswarteschlange, Fehlerbehandlung, Wiederholungsversuche und das schwierige Problem der korrekten Sperrung verwaltet werden, um nur einen sicherzustellen Der Server nimmt den nächsten Artikel auf.

... wir haben festgestellt, dass wir keine Hintergrundverarbeitungs-Engines schreiben, also haben wir nach vorhandenen Lösungen gesucht und sind mit dem großartigen OSS-Projekt gelandet Hangfire gelandet

Sergey Odinokov hat ein echtes Juwel geschaffen, mit dem man ganz einfach anfangen kann und das es Ihnen ermöglicht, das Backend auszutauschen, wie die Arbeit fortbesteht und in die Warteschlange gestellt wird. Hangfire verwendet Hintergrundthreads, behält jedoch die Jobs bei, verarbeitet Wiederholungsversuche und gibt Ihnen Einblick in die Arbeitswarteschlange. Hangfire-Jobs sind also robust und überstehen alle Unwägbarkeiten von Appdomains, die recycelt werden usw.

Die Grundeinstellung verwendet SQL Server als Speicher, Sie können jedoch Redis oder MSMQ austauschen, wenn die Zeit für eine Skalierung gekommen ist. Es verfügt außerdem über eine hervorragende Benutzeroberfläche zur Visualisierung aller Jobs und ihres Status sowie zum erneuten Einreihen von Jobs in die Warteschlange.

Mein Punkt ist, dass es zwar durchaus möglich ist, in einem Hintergrund-Thread das zu tun, was Sie wollen, aber viel Arbeit erforderlich ist, um ihn skalierbar und robust zu machen. Es ist in Ordnung für einfache Workloads, aber wenn die Dinge komplexer werden, bevorzuge ich die Verwendung einer speziell erstellten Bibliothek, anstatt diese Anstrengungen zu unternehmen.

Weitere Informationen zu den verfügbaren Optionen finden Sie in Scott Hanselmans Blog Informationen dem einige Optionen für die Bearbeitung von Hintergrundjobs in asp.net beschrieben werden. (Er gab Hangfire eine glühende Bewertung)

Wie von John erwähnt, lohnt es sich, in Phil Haacks Blog zu lesen, warum der Ansatz problematisch ist und wie die Arbeit am Thread ordnungsgemäß beendet werden kann, wenn die Appdomain entladen wird.

Avner
quelle
2

Können Sie einen Windows-Dienst für diese Aufgabe erstellen? Verwenden Sie dann .NET-Remoting vom Webserver, um den Windows-Dienst aufzurufen und die Aktion auszuführen. Wenn das der Fall ist, würde ich das tun.

Dies würde die Notwendigkeit beseitigen, IIS weiterzuleiten, und einen Teil seiner Verarbeitungsleistung einschränken.

Wenn nicht, würde ich den Benutzer zwingen, dort zu sitzen, während der Vorgang abgeschlossen ist. Auf diese Weise stellen Sie sicher, dass es vollständig ist und nicht von IIS getötet wird.

David Basarab
quelle
2

Es scheint eine unterstützte Möglichkeit zu geben, langjährige Arbeit in IIS zu hosten. Workflow Services scheinen dafür ausgelegt zu sein, insbesondere in Verbindung mit Windows Server AppFabric . Das Design ermöglicht das Recycling von Anwendungspools, indem es die automatische Persistenz und Wiederaufnahme der Langzeitarbeit unterstützt.

Olly
quelle
2

Sie können Aufgaben im Hintergrund ausführen und sie werden auch nach Beendigung der Anforderung ausgeführt. Lassen Sie nicht zu, dass eine nicht erfasste Ausnahme ausgelöst wird . Normalerweise möchten Sie immer Ihre Ausnahmen werfen. Wenn eine Ausnahme in einem neuen Thread ausgelöst wird, stürzt der IIS-Arbeitsprozess - w3wp.exe - ab , da Sie sich nicht mehr im Kontext der Anforderung befinden. Dadurch werden auch alle anderen Hintergrundaufgaben, die Sie ausgeführt haben, zusätzlich zu den in Arbeit befindlichen speichergestützten Sitzungen beendet, wenn Sie sie verwenden. Dies wäre schwer zu diagnostizieren, weshalb von der Praxis abgeraten wird.

Timothy Gonzalez
quelle
1

Erstellen Sie einfach einen Ersatzprozess, um die asynchronen Aufgaben auszuführen. Es muss kein Windows-Dienst sein (obwohl dies in den meisten Fällen der optimalere Ansatz ist. MSMQ ist weit über dem Kill.

Matt Davison
quelle