Webservices asincroni

di Andrea Zani, in .NET,

Quando si utilizzano webservices per servizi nelle nostre pagine asp.net, quale tecnica è meglio utilizzare per non ricorrere in attese infinite e non lasciare l'utente in attesa dell'elaborazione con la sua conseguente reazione di ripetitivi e violenti reload (chi non ha mai premuto ripetutamente sul tasto F5 non vedendo la risposta della pagina alzi la mano!)? E chi è al corrente dell'inutilità di questa operazione e della sua effettiva pericolosità?

Per evitare questo problema richiamerò i metodi del web-service in modalità asincrona. Ecco una tecnica modificata da un documento ufficiale Microsoft. Per testarla creiamo un web-services dalle prestazioni volutamente limitate:

namespace AsincronoWS
{
 public class Attesa : System.Web.Services.WebService
 {
  public Attesa()
  {
  }
  [WebMethod]
  public DateTime Ritardo(int secondi)
  {
   // Sleep ferma l'esecuzione per x millisecondi
   System.Threading.Thread.Sleep(secondi*1000);
   return DateTime.Now;
  }
 }
}

Questo web-services ha una sola funzione: Ritardo. Essa accetta come parametro un interno che è il tempo in secondi in cui esso fermerò la sua elaborazione. Alla fine ritorna come valore la data e ora odierna. Se guardiamo la classe con un Reflector, o dopo aver creato il proxy per questo web-service, vedremo oltre la funzione Ritardo che possiamo richiamare direttamente, anche due funzioni aggiuntive:

  • BeginRitardo
  • EndRitardo

Entrambe vengono create per il richiamo della funzione in modo asincrono. Creiamo quindi la pagina che richiamerà questo web-services. Sia che utilizziamo Visual Studio o WebMatrix, creiamo un web-references a questo web-services con la tecniche ormai note. Ecco il codice della prima pagina default.aspx che richiamerà il web-services:

   ws.Attesa a=new ws.Attesa();
   string xid=Guid.NewGuid().ToString();
   SingolaRichiesta2 sr=new SingolaRichiesta2(xid,a);
   AsyncCallback ritorno=new AsyncCallback(ElaboraRisposta2);
   a.BeginRitardo(10,ritorno,sr);
   Response.Redirect("attendi2.aspx?id="+xid,false);

ws è il namespace del web-service. Dopo aver istanziato la classe creiamo una guid univoca (la funzione Guid.NewGuid() crea una stringa alfanumerica univoca, con una possibilità di replica su 2^128, dunque un certo margine di sicurezza). Quindi viene istanziata la classe SingolaRichiesta2 con il valore del Guid e un riferimento dell'istanza del web-service. Di seguito viene istanziata la classe AsyncCallBack nel quale sarà inserito un riferimento alla funzione che sarà richiamata alla fine della chiamata ascincrona al web-service. De seguito, con BeginRequest passiamo come primo parametro il tempo di ritardo per la funzione Ritardo del web-service, il riferimento alla funzione di ritorno che elaborerà la risposta della funzione e la classe SingolaRichiesta che è utilizzata per lo scambio di informazioni tra la pagina chiamante e quella che riceverà risposta. Infine viene eseguito il redirect alla pagina "attendi2.aspx" passando come parametro nell'url, il Guid prima generato.

Vediamo ora la classe SingolaRichista:

public class SingolaRichiesta2
{
 string _xid;
 ws.Attesa _webservice;
 public SingolaRichiesta2(string xid, ws.Attesa webservice)
 {
  _xid=xid;
  _webservice=webservice;
 }
 public string ID
 {
  get { return _xid; }
 }
 public ws.Attesa WebService
 {
  get { return _webservice; }
 }
}

E la funzione che elaborerà il risultato del web-service richiamato in modo asincrono:

public void ElaboraRisposta2(IAsyncResult ar)
{
 SingolaRichiesta2 sr=ar.AsyncState as SingolaRichiesta2;
 ws.Attesa a=sr.WebService;
 Cache.Add(sr.ID,a.EndRitardo(ar),
  null,
  DateTime.Now.AddMinutes(3),
  TimeSpan.Zero,
  CacheItemPriority.NotRemovable,
  null);
}

Nella prima riga di codice riprendiamo la classe condivisa nella quale riprendiamo l'istanza del web-service chiamato. Quindi inseriamo un oggetto Cache. Come key inseriamo la Guid creata precedentemente e memorizzata nella classe condivisa SingolaRichiesta2. Come valore inseriamo la risposta della funzione asincrona richiamata, grazie alla funzione EndRitardo. Inoltre sono stati specificate le opzioni per la scadenza di questo oggetto (in questo caso l'oggetto sarà distrutto dopo tre minuti dalla creazione e potrà essere solo cancellato dopo questo tempo grazie all'opzione CacheItemPriority.NotRemovable).

La pagina "attesa2.aspx" è la pagina che sarà visualizzata per avvertire l'utente dell'attesa, e ci permetterà che esso non provochi danni in caso di ripetuti refresh. Ecco il codice HTML:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" >
<HTML>
<HEAD>
<title>Attendi</title>
<meta http-equiv="refresh" content='<asp:literal runat="server" id="ritardo" />'>
</meta>
</HEAD>
<body>
Attendi...
</body>
</HTML>

E il codice in C#:

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

Nel webcontrol Literal viene inserito un valore numerico per il refresh di questa pagina. Quindi viene controllato se esista in Cache un oggetto con la Guid passata come parametro. In caso negativo viene visualizzata normalmente la pagina, e grazie al meta refresh dopo il tempo scritto in precedenza sarà rifatto il reload sulla stessa pagina e sarà effettuato ancora il controllo all'interno dell'oggetto Cache. In caso esista si esegue un Redirect alla pagina "fine2.aspx":

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;
 Response.Write(dt2.ToString());
}

Con la Guid passata nell'url, possiamo riprendere il valore inserito in Cache che è il risultato della funzione asincrona.

Richiamare i web-services in modo asincrono ci permette di non lasciare le nostre pagine con tempi di attesa pericolosi e di dare all'utente informazioni sulle azioni che stiamo eseguendo. Inoltre, non occupiamo thread di esecuzione che per le pagine asp.net non sono infiniti, migliorando nello stesso tempo le prestazioni globali in caso di largo utilizzo dei web-services. L'utilizzo dell'oggetto Cache ci permette anche minor sforzo nella scrittura di codice visto che penserà lui stesso a distruggere gli oggetti inutili dopo il tempo prestabilito, garantendo in questo modo un uso ottimale delle risorse. Inoltre non utilizzando oggetti Session o Cookie per la memorizzazione dei dati tra pagine, ci garantiamo il funzionamento regolare anche con browser con i cookies disabilitati.

Si potrebbe migliorare ulteriormente questo approcio, ma questa cosa la sto trattando con la terza versione (qui è presente la seconda!).

Oggi ho fatto 30km in bici con la temperatura di 3°. Forse il tutto è solo delirio.

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