Richiamare funzioni particolarmente lente...

di Andrea Zani, in .NET,

Nel mio blog precedente ho descritto un possibile metodo per richiamare dei web services in modo asincrono, evitando eventuali blocchi del server in attesa della risposta del server remoto.

In questo blog tratterò invece una tecnica per richiamare funzioni di una nostra classe in modo asincrono, in modo che se tale funzione occupa molto tempo per la sua elaborazione il thread per l'elaborazione della pagina non rimane bloccato in attesa della fine dell'operazione.

La prima tecnica che potrebbe venire in mente in questi casi è l'utilizzo di delegate asincroni. Questa soluzione non è attuabile per il modo con cui vengono elaborati e gestiti i thread all'interno da parte del framework per le webapplication. Infatti sono presenti dei limiti di thread utilizzabili per le richieste (di base, 20, ma modificabile fino a 100 mettendo mano al machine.config) e l'uso dei delegate asincroni decurta il numero di thread disponibili bloccando eventuali altre richieste in entrata giunti al limite. Se per esempio per le richieste sono occupati 19 thread dei 20 disponibili, e una di queste pagine asp.net richiama una funzione con un BeginInvoke su un delegate, saranno occupati tutti i 20 thread disponibili bloccando in questo modo nuove richieste; e se fossero più d'una le pagine che richiamano funzioni asincrone in questo modo la cosa comporterebbe cali di prestazioni evidenti. Parlerò di questa cosa in futuro.

Per risolvere possiamo creare nostri Thead indipendenti che non hanno questi problemi. Dunque proviamo a scrivere un esempio in cui una pagina richiama una funzione particolarmente pesante (qui simulata con un Sleep) in modo asincrono; visualizzeremo anche una pagina che ci avvisa dell'attesa e solo alla fine sarà visualizzata una pagina con il risultato dell'operazione. Innanzitutto vediamo, da un'ipotetica pagina iniziale, come richiamare una funzione creando un thread apposito per l'elaborazione dello stesso che non decurti il già numero esiguo disponibile per asp.net:

string xid=Guid.NewGuid().ToString();
ClasseLenta cl=new ClasseLenta(3,xid,HttpContext.Current);
ThreadStart ts=new ThreadStart(cl.Ritardo);
Thread t=new Thread(ts);
t.Start();
Response.Redirect("attendix.aspx?id="+xid,false);

Sto utilizzando una tecnica simile a quella già utilizzata per i webservices del blog precedente. Grazie ad un codice univoco creato con la funzione "NewGuid" potremo riprendere il risultato della nostra funzione anche se nello stesso tempo molte altre pagine stanno richiamando questa lenta funzione.

La classe con la funzione interessata, in questo esempio, si chiama "ClasseLenta" e accetta come argomento il parametro da passase alla funzione, l'identificatore univoco e l'HttpContext attuale, in modo che possiamo referenziare la classe Cache della nostra webapplication. Dopo aver avviato il thread viene richiamata la pagina per l'attesa con l'identificatore passato come parametro all'interno dell'url di pagina. Questo ci consente l'indipendenza da Session o cookie (altra possibile tecnica per portarci dietro l'identificatore).

Il codice della pagina "attentix.aspx" è il seguente:

<%@ Page language="c#" Codebehind="attendix.aspx.cs" AutoEventWireup="false" Inherits="Asincrono.attendix" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
 <HEAD>
  <title>attendix</title>
  <meta http-equiv="refresh" content='<asp:literal runat="server" id="ritardo" />'>
 </HEAD>
 <body>
  <form id="Form1" method="post" runat="server">
   Attendi...
  </form>
 </body>
</HTML>

E il codice:

private void Page_Load(object sender, System.EventArgs e)
{
 ritardo.Text="2";
 object dt=Cache[Request.Params["id"].ToString()];
 if (dt==null) return;
 Response.Redirect("finex.aspx?id="+Request.Params["id"].ToString(),false);
}

Viene controllato se è presente nell'oggetto Cache il risultato della "lenta" funzione; se non è presente viene fatto il refresh della pagina, altrimenti viene fatto il redirect alla pagina che visualizza il risultato. Prima di vedere questa pagina, ecco la classe interessata:

public class ClasseLenta
{
 int ritardo;
 string xid;
 HttpContext context;
 public ClasseLenta(int Ritardo_in_secondi,string Identificativa,HttpContext current)
 {
  ritardo=Ritardo_in_secondi;
  xid=Identificativa;
  context=current;
 }
 public void Ritardo()
 {
  Thread.Sleep(ritardo*1000);
  DateTime ritorno=DateTime.Now;
  context.Cache.Add(xid,ritorno,
   null,
   DateTime.Now.AddSeconds(20),
   TimeSpan.Zero,
   CacheItemPriority.NotRemovable,
   null);
 }
}

Dopo che la classe è stata istanziata con i parametri prima citati, la funzione chiamata con il thread, è "Ritardo". Questa esegue l'operazione richiesta (qui simulato il ritardo con Sleep) e inserito il risultato nell'oggetto Cache usando come chiave l'identificatore.

Il codice della pagina che visualizza la fine delle operazioni "finex.aspx" è banalmente questo:

private void Page_Load(object sender, System.EventArgs e)
{
 object dt=Cache[Request.Params["id"].ToString()];
 if (dt==null)
 {
  Label1.Text="Istanza già conclusa";
  return;
 }
 // Visualizzo i dati
 DateTime dt2=(DateTime)dt;
 Label1.Text=(dt2.ToString());
}

E viene visualizzato il valore restituito dalla lenta funzione. Semplice e funzionale, anche se qui il tutto è molto grezzo e da migliorare. Da modificare i tempi di cancellazione dell'oggetto cache e del refresh della pagina di attesa.

Tra qualche giorno è Natale.

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Nella stessa categoria
I più letti del mese