Posso utilizzare i thread per eseguire processi di lunga durata su IIS?


85

In un'applicazione ASP.Net, l'utente fa clic su un pulsante nella pagina Web e questo crea un'istanza di un oggetto sul server tramite il gestore di eventi e chiama un metodo sull'oggetto. Il metodo passa a un sistema esterno per eseguire operazioni e ciò potrebbe richiedere del tempo. Quindi, quello che vorrei fare è eseguire quella chiamata al metodo in un altro thread in modo da poter restituire il controllo all'utente con "La tua richiesta è stata inviata". Sono ragionevolmente felice di farlo come fuoco e dimentica, anche se sarebbe ancora più bello se l'utente potesse continuare a interrogare l'oggetto per lo stato.

Quello che non so è se IIS consente al mio thread di continuare a funzionare, anche se la sessione dell'utente scade. Immagina, l'utente attiva l'evento e noi istanziamo l'oggetto sul server e attiviamo il metodo in un nuovo thread. L'utente è soddisfatto del messaggio "La tua richiesta è stata inviata" e chiude il browser. Alla fine, questa sessione di utenti scadrà su IIS, ma il thread potrebbe essere ancora in esecuzione, funzionando. IIS consentirà al thread di continuare a funzionare o lo interromperà e smaltirà l'oggetto una volta scaduta la sessione utente?

EDIT: Dalle risposte e dai commenti, capisco che il modo migliore per farlo è spostare l'elaborazione di lunga durata al di fuori di IIS. Oltre a tutto il resto, si tratta del problema del riciclaggio del dominio app. In pratica, ho bisogno di ottenere la versione 1 da terra in un tempo limitato e devo lavorare all'interno di un framework esistente, quindi vorrei evitare il livello di servizio, da qui il desiderio di sparare semplicemente dal thread all'interno di IIS. In pratica, la "lunga durata" qui sarà solo di pochi minuti e la concorrenza sul sito web sarà bassa, quindi dovrebbe andare bene. Ma la prossima versione dovrà sicuramente essere suddivisa in un livello di servizio separato.


1
Hai ulteriori dettagli su ciò che deve essere elaborato e sul tuo ambiente di hosting? Ad esempio, sei in grado di installare un servizio?
senfo

Puoi sempre utilizzare i metodi di programmazione Async e Await (Visual Studio 2012+). Molto più pulito che gestire i thread da solo.
SausageFingers

Risposte:


65

Puoi realizzare ciò che desideri, ma in genere è una cattiva idea. Diversi blog ASP.NET e motori CMS adottano questo approccio, perché vogliono essere installabili su un sistema di hosting condiviso e non dipendono da un servizio Windows che deve essere installato. In genere avviano un thread a lunga esecuzione in Global.asax all'avvio dell'app e hanno quel processo di thread in coda per le attività.

Oltre a ridurre le risorse disponibili per IIS / ASP.NET per elaborare le richieste, hai anche problemi con il thread che viene terminato quando AppDomain viene riciclato e quindi devi gestire la persistenza dell'attività mentre è in volo, come così come riavviare il lavoro quando l'AppDomain ritorna.

Tieni presente che in molti casi AppDomain viene riciclato automaticamente a un intervallo predefinito, così come se aggiorni il web.config, ecc.

Se riesci a gestire la persistenza e gli aspetti transazionali del tuo thread che viene ucciso in qualsiasi momento, puoi aggirare il riciclaggio di AppDomain avendo un processo esterno che fa una richiesta sul tuo sito a un certo intervallo, in modo che se il sito viene riciclato tu sono garantiti per riavviarlo automaticamente entro X minuti.

Di nuovo, questa è in genere una cattiva idea.

EDIT: ecco alcuni esempi di questa tecnica in azione:

Community Server: utilizzo di servizi Windows e thread in background per eseguire codice a intervalli pianificati Creazione di un thread in background al primo avvio del sito web

EDIT (dal lontano futuro) - In questi giorni userei Hangfire .


1
Grazie, questo è perspicace. Il riciclaggio è decisamente un killer e da quello che stai dicendo deduco che posso farlo come una cosa difficile e veloce, ma è pericoloso.
Frans

Sì, qui controlla questo: professionalaspnet.com/archive/2007/09/02/… Ed ecco un thread su come COmmunity Server gestisce la stessa cosa: dev.communityserver.com/forums/t/459942.aspx
Bryan Batchelder

Nel mio caso, sono stato in grado di dire a IIS di non riciclare mai e di non fermare mai il sito. In questo modo ho potuto lavorare in base a questo presupposto e che il sito sarebbe stato riciclato solo durante la distribuzione e ogni volta che si verificava la manutenzione pianificata. Dato che stavo solo facendo un sito di tipo amministratore, questo ha funzionato perfettamente per me.
Brett

1
Bizzarro che questo abbia così tanti voti positivi. Che questa cosa sia possibile è una delle cose più interessanti di ASP.NET: che tutte le richieste vengono servite nello stesso AppDomain insieme a qualsiasi thread in background che è possibile creare. Nessuno dei motivi addotti per cui questa è una "cattiva idea" mi sembra convincente. AppDomains può non funzionare, sì, ma questo non è univoco per l'ambiente ASP.NET. Devi giocare bene con il tuo ambiente di hosting (google per HostingEnvironment.RegisterObject).
Giovanni

3
.NET 4.5.2 ha aggiunto una nuova funzionalità QueueBackgroundWorkItemper registrare facilmente le attività in background, potresti voler aggiornare di nuovo la tua risposta.
Scott Chamberlain

40

Non sono d'accordo con la risposta accettata.

L'utilizzo di un thread in background (o un'attività iniziata con Task.Factory.StartNew) va bene in ASP.NET. Come con tutti gli ambienti di hosting, potresti voler comprendere e collaborare con le strutture che regolano l'arresto.

In ASP.NET, è possibile registrare il lavoro che deve essere interrotto correttamente all'arresto utilizzando il HostingEnvironment.RegisterObjectmetodo. Vedi questo articolo e i commenti per una discussione.

(Come sottolinea Gerard nel suo commento, ora c'è anche la HostingEnvironment.QueueBackgroundWorkItemchiamata a RegisterObjectper registrare uno scheduler su cui lavorare l'elemento in background. Nel complesso il nuovo metodo è più bello poiché è basato su attività.)

Per quanto riguarda il tema generale di cui spesso senti parlare come una cattiva idea, considera l'alternativa di distribuire un servizio Windows (o un altro tipo di applicazione extra-processo):

  • Niente più banali implementazioni con la distribuzione web
  • Non distribuibile esclusivamente su siti Web di Azure
  • A seconda della natura dell'attività in background, i processi dovranno probabilmente comunicare. Ciò significa che una qualche forma di IPC o il servizio dovrà accedere a un database comune.

Si noti inoltre che alcuni scenari avanzati potrebbero persino richiedere che il thread in background venga eseguito nello stesso spazio degli indirizzi delle richieste. Vedo il fatto che ASP.NET può farlo come un grande vantaggio che è diventato possibile grazie a .NET.


Questo è fantastico. Ho un'attività che richiede circa 30 secondi per essere completata. Perfetto se devi solo ritardare il pool di app abbastanza a lungo per portare a termine il lavoro.
Scuba Steve

1
Da .Net Framework 4.5.2 puoi anche usare HostingEnvironment.QueueBackgroundWorkItem (Action <CancellationToken>)
Gerard Carbó

Nelle mie osservazioni sembra che IIS alla fine arresti l'API Web ASP.NET che sto eseguendo e non lo riavvii fino a quando non arriva una nuova richiesta. Quindi all'ora stabilita in cui il mio thread dovrebbe avviare l'applicazione potrebbe nemmeno essere in esecuzione. Mi sbaglio su questo?
David Sopko

7

Non si desidera utilizzare un thread dal pool di thread IIS per questa attività perché lascerebbe quel thread in grado di elaborare le richieste future. Potresti esaminare le pagine asincrone in ASP.NET 2.0 , ma nemmeno quella sarebbe la risposta giusta. Invece, quello che sembra che trarrai vantaggio è esaminare Accodamento messaggi Microsoft . In sostanza, aggiungeresti i dettagli dell'attività alla coda e un altro processo in background (possibilmente un servizio Windows) sarebbe incaricato di eseguire tale attività. Ma la conclusione è che il processo in background è completamente isolato da IIS.


Ah, MSMQ - una delle mie tecnologie preferite a lungo termine. Grazie per l'intuizione e il collegamento.
Frans

6

Suggerirei di utilizzare HangFire per tali requisiti. È un bel motore che spara e dimentica che gira in background, supporta un'architettura diversa, affidabile perché è supportato dall'archiviazione di persistenza.


5

C'è un buon thread e un codice di esempio qui: http://forums.asp.net/t/1534903.aspx?PageIndex=2

Ho persino accarezzato l'idea di chiamare una pagina Keep Alive sul mio sito Web dal thread per mantenere vivo il pool di app. Tenere presente che se si utilizza questo metodo è necessaria una gestione del ripristino davvero ottimale, poiché l'applicazione potrebbe essere riciclata in qualsiasi momento. Come molti hanno già detto, questo non è l'approccio giusto se hai accesso ad altre opzioni di servizio, ma per l'hosting condiviso questa potrebbe essere una delle tue uniche opzioni.

Per mantenere attivo il pool di app, puoi effettuare una richiesta al tuo sito durante l'elaborazione del thread. Ciò può aiutare a mantenere in vita il pool di app se il processo viene eseguito a lungo.

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;
    }

5

Abbiamo iniziato questo percorso e in realtà ha funzionato bene quando la nostra app era su un server. Quando volevamo scalare su più macchine (o utilizzare più w3wp in un web garen) dovevamo rivalutare e vedere come gestire una coda di lavoro, la gestione degli errori, i tentativi e il complicato problema del blocco corretto per assicurarne solo uno il server preleva l'elemento successivo.

... ci siamo resi conto che non siamo nel business della scrittura di motori di elaborazione in background, quindi abbiamo cercato soluzioni esistenti e siamo arrivati ​​a utilizzare il fantastico progetto OSS hangfire

Sergey Odinokov ha creato un vero gioiello con cui è davvero facile iniziare e ti consente di sostituire il backend di come il lavoro viene mantenuto e messo in coda. Hangfire utilizza thread in background, ma persiste i lavori, gestisce i nuovi tentativi e ti dà visibilità nella coda di lavoro. Quindi i lavori impiccati sono robusti e sopravvivono a tutti i capricci degli appdomain che vengono riciclati ecc.

La sua configurazione di base utilizza il server sql come archiviazione, ma puoi sostituirlo con Redis o MSMQ quando è il momento di aumentare. Ha anche un'eccellente interfaccia utente per visualizzare tutti i lavori e il loro stato, oltre a consentire di rimettere in coda i lavori.

Il punto è che mentre è del tutto possibile fare ciò che vuoi in un thread in background, c'è molto lavoro per renderlo scalabile e robusto. Va bene per carichi di lavoro semplici, ma quando le cose si fanno più complesse preferisco di gran lunga utilizzare una libreria appositamente creata piuttosto che eseguire questo sforzo.

Per qualche prospettiva in più sulle opzioni disponibili, controlla il blog di Scott Hanselman che copre alcune opzioni per la gestione dei lavori in background in asp.net. (Ha dato a hangfire una recensione brillante)

Inoltre, come riferito da John, vale la pena leggere il blog di Phil Haack sul perché l'approccio è problematico e su come interrompere con grazia il lavoro sul thread quando appdomain viene scaricato.


2

Puoi creare un servizio Windows per eseguire tale operazione? Quindi utilizzare .NET remoting dal server Web per chiamare il servizio Windows per eseguire l'azione? Se è così, è quello che farei.

Ciò eliminerebbe la necessità di inoltrare su IIS e limiterebbe parte della sua potenza di elaborazione.

In caso contrario, costringerei l'utente a sedersi lì mentre il processo è finito. In questo modo ti assicuri che sia completato e non ucciso da IIS.


2

Sembra esserci un modo supportato per ospitare il lavoro di lunga durata in IIS. I servizi del flusso di lavoro sembrano progettati per questo, soprattutto in combinazione con Windows Server AppFabric . Il design consente il riciclaggio del pool di applicazioni supportando la persistenza automatica e la ripresa del lavoro di lunga durata.


2

È possibile eseguire attività in background e verranno completate anche al termine della richiesta. Non lasciare che venga generata un'eccezione non rilevata . Normalmente vuoi sempre lanciare le tue eccezioni. Se viene generata un'eccezione in un nuovo thread, si bloccherà il processo di lavoro di IIS - w3wp.exe , perché non sei più nel contesto della richiesta. Questo ucciderà anche qualsiasi altra attività in background che hai in esecuzione oltre alle sessioni supportate dalla memoria in-process se le stai utilizzando. Questo sarebbe difficile da diagnosticare, motivo per cui la pratica è scoraggiata.


1

Basta creare un processo surrogato per eseguire le attività asincrone; non deve essere un servizio Windows (sebbene questo sia l'approccio più ottimale nella maggior parte dei casi. MSMQ è decisamente oltre kill.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.