Esecuzione di un’operazione asincrona triggersta da una richiesta di una pagina Web ASP.NET

Ho un’operazione asincrona che per vari motivi deve essere triggersta utilizzando una chiamata HTTP a una pagina Web ASP.NET. Quando viene richiesta la mia pagina, dovrebbe iniziare questa operazione e restituire immediatamente un riconoscimento al client.

Questo metodo è anche esposto tramite un servizio Web WCF e funziona perfettamente.

Al mio primo tentativo, è stata lanciata un’eccezione, dicendomi:

  Le operazioni asincrone non sono consentite in questo contesto.
 La pagina che inizia un'operazione asincrona deve avere l'Async
 attributo impostato su true e un'operazione asincrona può essere solo
 avviato su una pagina prima dell'evento PreRenderComplete. 

Quindi ovviamente ho aggiunto il parametro Async="true" alla direttiva @Page . Ora, non ricevo un errore, ma la pagina sta bloccando fino al completamento dell’operazione asincrona.

Come posso far funzionare una vera e propria pagina ignifuga?

Modifica: qualche codice per maggiori informazioni. È un po ‘più complicato di così, ma ho cercato di ottenere l’idea generale.

 public partial class SendMessagePage : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { string message = Request.QueryString["Message"]; string clientId = Request.QueryString["ClientId"]; AsyncMessageSender sender = new AsyncMessageSender(clientId, message); sender.Start(); Response.Write("Success"); } } 

La class AsyncMessageSender:

 public class AsyncMessageSender { private BackgroundWorker backgroundWorker; private string client; private string msg; public AsyncMessageSender(string clientId, string message) { this.client = clientId; this.msg = message; // setup background thread to listen backgroundThread = new BackgroundWorker(); backgroundThread.WorkerSupportsCancellation = true; backgroundThread.DoWork += new DoWorkEventHandler(backgroundThread_DoWork); } public void Start() { backgroundThread.RunWorkerAsync(); } ... // after that it's pretty predictable } 

Se non ti interessa restituire nulla all’utente, puoi semplicemente triggersre un thread separato o per un approccio rapido e sporco, utilizzare un delegato e richiamarlo in modo asincrono. Se non ti interessa avvisare l’utente quando termina l’attività asincrona, puoi ignorare il callback. Prova a inserire un punto di interruzione alla fine del metodo SomeVeryLongAction () e vedrai che termina dopo che la pagina è già stata pubblicata:

 private delegate void DoStuff(); //delegate for the action protected void Page_Load(object sender, EventArgs e) { } protected void Button1_Click(object sender, EventArgs e) { //create the delegate DoStuff myAction = new DoStuff(SomeVeryLongAction); //invoke it asynchrnously, control passes to next statement myAction.BeginInvoke(null, null); Button1.Text = DateTime.Now.ToString(); } private void SomeVeryLongAction() { for (int i = 0; i < 100; i++) { //simulation of some VERY long job System.Threading.Thread.Sleep(100); } } 

OK, ecco il problema: l’attributo Async è per il caso in cui la tua pagina chiamerà qualche attività di lunga durata che blocca anche il thread, e quindi la tua pagina ha bisogno dell’output di quell’attività per restituire informazioni all’utente. Ad esempio, se la tua pagina ha bisogno di chiamare un servizio web, aspetta la sua risposta, e poi usa i dati dalla risposta per rendere la tua pagina.

Il motivo per cui utilizzi l’attributo Async è per evitare di bloccare il thread. Questo è importante perché le applicazioni ASP.NET utilizzano un pool di thread per servire le richieste e c’è solo un numero relativamente piccolo di thread disponibili. E se ogni chiamata attacca il thread mentre attende la chiamata al servizio web, presto colpirà abbastanza utenti concorrenti che gli utenti dovranno attendere fino al completamento di queste chiamate al servizio web. L’attributo Async consente al thread di tornare al pool di thread e servire altri visitatori simultanei al tuo sito web, piuttosto che forzarlo a stare fermo senza fare nulla mentre attende che la chiamata al servizio web torni.

Il risultato per te è questo: l’attributo Async è progettato per il caso in cui non è ansible eseguire il rendering della pagina fino al completamento dell’attività asincrona, ed è per questo che non esegue il rendering della pagina immediatamente.

È necessario avviare il proprio thread e renderlo un thread daemon. Non ricordo la syntax esatta per quello, ma puoi facilmente trovarlo nel documento cercando nel documento BCL “demone”. Ciò significa che il thread impedirà la chiusura della tua applicazione mentre è triggers, il che è importante perché ASP.NET e IIS si riservano il diritto di “riciclare il tuo processo” quando lo ritengono necessario, e se ciò accade mentre il tuo thread sta funzionando, il tuo compito verrà fermato. Fare in modo che il daemon del thread impedisca questo (eccetto alcuni possibili rari casi limite … scoprirai di più quando trovi la documentazione su questo).

Quel thread daemon è il punto in cui avvierai queste attività. E dopo che hai detto al thread demone di eseguire l’operazione, puoi immediatamente rendere la tua pagina … quindi il rendering della pagina avverrà immediatamente.

Anche meglio di un thread daemon nel tuo processo ASP.NET, però, sarebbe quello di implementare un servizio di Windows per eseguire l’attività. Richiedere all’applicazione ASP.NET di comunicare l’attività da eseguire al servizio. Non c’è bisogno di un thread daemon e non c’è bisogno di preoccuparsi che il processo ASP.NET venga riciclato. Come si dice al servizio di eseguire l’operazione? Forse tramite WCF, o forse inserendo un record in una tabella di database che il servizio esegue il polling. O in un certo numero di altri modi.

EDIT: Ecco un’altra idea, che ho usato prima per questo stesso scopo. Scrivi le informazioni sulla tua attività in una coda MSMQ. Avere un altro processo (magari anche su un’altra macchina) estrae da quella coda e svolgere l’attività dispendiosa in termini di tempo. Il lavoro di inserimento in una coda è ottimizzato per restituire il più rapidamente ansible, in modo che il thread non si blocchi mentre i dati inseriti nella coda vengono inviati attraverso il filo o qualcosa del genere. È uno dei modi più veloci per prendere nota del fatto che un’attività deve essere eseguita senza attendere che l’attività venga eseguita.

Se stai eseguendo webforms, imposta Ansync = “true” nella tua pagina .aspx dove stai facendo la richiesta.
<%@ Page Language="C#" Async="true" ... %>

È ansible aggirare questa limitazione abbastanza facilmente e senza nemmeno impostare Async su true.

 public void Start() { new Task(() => { backgroundThread.RunWorkerAsync(); }).Start(); } 

Se ricevi questo errore quando chiami il servizio web in modo asincrono, assicurati di aggiungere l’attributo Async = ‘true’ come indicato dal messaggio di eccezione?

inizio pagina