Magazine

Web services asincroni

Moris Bozzetto

22/11/2006

Utilizzare i webservices in modo asincrono

0%100%
per esprimere un voto รจ necessario registrarsi al sito

Visual Studio

 

Spesso quando si realizzano applicazioni che utilizzano webservices, è necessario effettuare chiamate asincrone per “consumare” i dati. Vediamo cosa ci mette a disposizione il framework e impariamo a scegliere la soluzione che si adatta alle nostre esigenze.

Soluzione asincrona lato client

Se il webservice espone un metodo GetItems, troviamo nella classe proxy anche la coppia di metodi BeginGetItems e EndGetItems.

public System.IAsyncResult BeginGetItems(System.AsyncCallback callback, object asyncState)

{

return this.BeginInvoke("GetItems", new object[0], callback, asyncState);

}

public System.Data.DataSet EndGetItems(System.IAsyncResult asyncResult)

{

object[] results = this.EndInvoke(asyncResult);

return ((System.Data.DataSet)(results[0]));

}

Il client esegue il metodo BeginGetItems, indicando la funzione di callback, ossia la funzione che sarà eseguita alla conclusione del webMethod.

private void button1_Click(object sender, System.EventArgs e)

{

WSSample.Service1 ws =new WSSample.Service1();

ws.BeginGetItems(new System.AsyncCallback(EndGetItems),ws);

}

Per semplicità è possibile utilizzare asyncState per passare alla funzione di callback un riferimento all’oggetto webservice, anziché ricorrere a variabili globali.

private void EndGetItems(IAsyncResult result)

{

WSSample.Service1 ws=(WSSample.Service1)result.AsyncState;

ds=ws.EndGetItems(result);

dataGrid1.Invoke(new MethodInvoker(BindData),null);

}

Il metodo GetItems in realtà non è eseguito in maniera asincrona dal server, ma in un thread parallelo sul nostro client. Per questo motivo, visto che le proprietà dei controlli sono modificabili solo dal thread principale, per eseguire il databing sulla griglia dobbiamo ricorrere a:

dataGrid1.Invoke(new MethodInvoker(BindData),null)

Il framework 2.0 semplifica ulteriormente il nostro codice, infatti, mentre GetItemsAsync avvia la chiamata asincrona, l’evento GetItemsCompleted rappresenta la callback che indica la fine dell’operazione.

ws.GetItemsCompleted += ws_GetItemsCompleted;

ws.GetItemsAsync();

void ws_GetItemsCompleted(object sender, WSSampleClient.WSSample.GetItemsCompletedEventArgs e)

{

ds = (DataSet)e.Result;

dataGrid1.Invoke(new MethodInvoker(BindData), null);

}

Il punto di forza di questa soluzione è sicuramente la velocità di implementazione, dato che troviamo tutto o quasi già fatto.

Personalmente penso che questa soluzione sia utile quando lo scopo è non lasciare il client bloccato in attesa della risposta, non tanto perché l’elaborazione lato server sia lenta o complessa, ma quanto perché la quantità di dati da restituire è tale da richiedere un certo tempo per il trasferimento o perché semplicemente la nostra connessione è lenta.

Soluzione asincrona lato server

ASP.net utilizza un pool di thread per evadere le richieste del client, una volta raggiunta la dimensione massima del pool, tutte le altre richieste attendono che una risorsa del pool si liberi. Per evitare che le richieste siano accodate perché i thread sono impegnati nell’esecuzione di attività di lunga durata, possiamo realizzare dei web services asincroni lato server, cioè eseguiti in un thread diverso da quello utilizzato per rispondere alle normali richieste del client.

Definiamo 2 webmethod simili a quelli visti nella soluzione precedente, BeginGetItemsAsyncServer avvierà un’operazione asincrona restituendo un oggetto IAsyncResult che il metodo EndGetItemsAsyncServer utilizzerà per concludere l’operazione e restituire il risultato al client.

public delegate DataSet DelegateGetItems();

private DelegateGetItems delegatews;

[WebMethod]

public IAsyncResult BeginGetItemsAsyncServer(AsyncCallback callback,object obj)

{

delegatews =new DelegateGetItems(CreateDataset);

return delegatews.BeginInvoke(callback,obj) ;

}

[WebMethod]

public DataSet EndGetItemsAsyncServer(IAsyncResult ar)

{

return delegatews.EndInvoke(ar);

}

Questa volta è il client a non accorgersi di scatenare una richiesta asincrona, infatti, il motore asp.net rende visibile un solo metodo: GetItemsAsyncServer().

L’esecuzione di questo metodo avvia sul server una richiesta asincrona e rende il thread al pool, salvo richiederne un’altro per il tempo necessario ad inviare la risposta al client.

Anche questa soluzione, seppur in momenti separati, occupa un elemento del pool (anche se per un breve periodo), ma risulta comunque molto adatta a elaborazioni particolarmente complesse e lunghe, che altrimenti potrebbero causare l’accodamento delle richieste del client.

Soluzione asincrona controllata periodicamente dal client

Quello che ci piacerebbe fare quando eseguiamo un’operazione asincrona è rendere libero il client di richiederla e verificarne periodicamente lo stato di avanzamento fino al completamento (dato che il protocollo http stateless per definizione non permette al server di comunicarci di sua iniziativa la conclusione dell’attività).

Una soluzione di questo tipo è realizzabile utilizzando dei componenti sul server esterni al processo asp.net, ad esempio CAO (Client Activated Object) esposti con .Net Remoting.

Con opportune configurazioni possiamo fare in modo che ogni oggetto si autodistrugga al trascorrere di un periodo di tempo, così da liberare le risorse qualora il client non concluda l’elaborazione correttamente

L’indipenza di questi oggetti dal processo Asp.net ci consente anche di mantenere il loro stato in situazioni critiche, come ad esempio il crash dell’applicazione web.

Sarà necessario un processo host che oltre a essere in ascolto delle richieste provenienti dal client, ospiterà l’oggetto remoting. Per realizzare l’host si ricorre solitamente ad un windows service, che viene avviato automaticamente all’avvio del sistema operativo.

La classe SampleClass, è il nostro CAO, ed espone tre metodi, rispettivamente per avviare, verificare la percentuale di completamento e concludere un’elaborazione.

public class SampleClass: MarshalByRefObject

{

private Thread thread;

private WorkerClass workerClass;

public SampleClass()

{

}

public void BeginWork()

{

workerClass =new WorkerClass(1000);

thread =new Thread(new ThreadStart(workerClass.Work));

thread.IsBackground =true;

thread.Start();

}

public int WorkStatus()

{

if (workerClass !=null)

{

return workerClass.CurrentItemsNumber*100)/workerClass.MaxItems);

}

else

return 100;

}

public DataSet EndWork()

{

if (workerClass !=null)

return workerClass.GetDataset();

else

return null;

}

Il processo host, realizzato con un’applicazione console, si occupa di esporre il nostro oggetto per metterlo in grado di rispondere alle richieste del nostro web service.

IChannel ch;

string port= ConfigurationSettings.AppSettings["Port"];

string appName=ConfigurationSettings.AppSettings["Application"];

ch = new TcpChannel(int.Parse(port));

ChannelServices.RegisterChannel(ch);

RemotingConfiguration.ApplicationName = appName;

RemotingConfiguration.RegisterActivatedServiceType(

typeof(RemotingSampleServer.SampleClass));

Il client, ottiene mediante BeginGetItemsRemotingServer, una sorta di “ticket” che identifica l’istanza dell’oggetto cao sul server e che sarà passato come parametro ai metodi CheckStatusGetItemsRemotingServer e EndGetItemsRemotingServer.

string ticket=ws.BeginGetItemsRemotingServer();

while (percent!=100)

{

System.Threading.Thread.Sleep(1000);

percent=ws.CheckStatusGetItemsRemotingServer(ticket); label1.Text =percent.ToString() + " %";

}

ds=ws.EndGetItemsRemotingServer(ticket);

E per ultimo il nostro webservice!

Per prima cosa registriamo l’oggetto CAO, per essere sicuri che la registrazione avvenga prima dell’esecuzione delle richieste client utilizziamo Application_Start().

Definiamo quindi i tre WebMethod che permetteranno al client di usare l’oggetto.

[WebMethod]

public string BeginGetItemsRemotingServer()

{

RemotingSampleServer.SampleClass server=null;

server=newemotingSampleServer.SampleClass(); server.BeginWork();

return RemotingHelper.GetURLForObject(server);

}

[WebMethod]

public int CheckStatusGetItemsRemotingServer(string objectid)

{

RemotingSampleServer.SampleClass server=null;

server=RemotingHelper.GetSampleClass(objectid);

if (server!=null)

return server.WorkStatus();

else

return 100;

}

[WebMethod]

public DataSet EndGetItemsRemotingServer(string objectid)

{

RemotingSampleServer.SampleClass server=null;

server=RemotingHelper.GetSampleClass(objectid);

if (server!=null)

return server.EndWork() ;

else

return null;

}

La classe RemotingHelper ci consente di ottenere l’indirizzo completo dell’istanza di RemotingSampleServer.SampleClass, che potremmo usare anche direttamente dal client, se conoscessimo la struttura dell’oggetto remoto.

Preferisco però restituire solo l’identificativo (ticket), in maniera da mantenere un maggiore livello di astrazione.

Penso sia utile ricorrere a soluzioni di questo tipo quando dobbiamo gestire elaborazioni complesse e/o di lunga durata (dell’ordine di qualche minuto), oppure quando lato server utilizziamo oggetti non realizzati con .net (es. COM) e vogliamo proteggere la web application da crash non previsti di questi componenti.

Spero di avervi dato qualche spunto su cui riflettere e soprattutto di avervi resi curiosi di provare .net remoting, tecnologia della quale sono un’estimatore, ma che forse non è molto conosciuta.

 

Bibliografia

.Net Full Contact XML & WEB SERVICES

Paolo Pialorsi

Mondadori Informatica

Advanced .Net Remoting

Ingo Rammer

Apress

ASP.NET 2.0 Async Tecniques

Roberto Brunetti

http://www.devleap.com

Commenti
Nome

Sito web
Commento


indietro