4 pagine in totale: <<Indietro 1 [2] 3 4 Avanti >>
Un approccio multi-threaded in un caso come questo può migliorare sensibilmente le cose: l'idea, infatti, è quella di delegare ad un thread secondario (detto worker thread) questa operazione lunga, magari reindirizzando nel frattempo l'utente in una pagina intermedia, dove visualizzare un messaggio di attesa. Il .NET Framework fornisce un supporto diretto al multi-threading tramite i tipi definiti all'interno del namespace System.Threading. In particolare, la classe System.Threading.Thread rappresenta l'alter-ego managed di un thread di Microsoft Windows (nota: in realtà ciò non è sempre vero, ma per la maggioranza dei casi pratici possiamo ritenere che lo sia) e consente di implementare il comportamento sopra descritto senza aumentare di molto la complessità del codice:
protected void btnGetPrices_Click(object sender, EventArgs e)
{
// istanzio uno ShopsExplorer e lo metto in session
ShopsExplorer explorer = new ShopsExplorer(this.txtISBN.Text);
Session["Explorer"] = explorer;
// eseguo l'explorer in un worker thread secondario
Thread thread = new Thread(new ThreadStart(explorer.GetPrices));
thread.Start(); // questo metodo ritorna immediatamente
// redirect sulla pagina dei risultati
Response.Redirect("MultiThreadedResults.aspx");
}
Come si vede dall'esempio, è sufficiente creare un'istanza di una classe Thread ed assegnarvi il metodo ShopsExplorer.GetPrices() da eseguire. La successiva invocazione Thread.Start() consente di avviare il thread secondario: questa operazione è chiamata in gergo fork, dato che in corrispondenza di essa il flusso di codice in esecuzione viene separato in due rami paralleli. Essa ritorna immediatamente, così che il processo di generazione della risposta non risulti più bloccato e possa redirigere il browser dell'utente alla pagina MultiThreadedResults.aspx, dove viene mostrato un messaggio di attesa. Nel frattempo, si è provveduto a memorizzare in Session l'istanza di ShopsExplorer, in modo da poterla recuperare in un secondo momento, quando i risultati saranno finalmente disponibili.
La pagina MultiThreadedResults è costituita da un oggetto MultiView che consente di visualizzare l'elenco dei risultati oppure un messaggio di attesa, a seconda che l'interrogazione sia terminata o meno. All'interno del metodo Page_Load, non si deve far altro che recuperare l'istanza di ShopsExplorer dalla sessione e verificarne lo stato:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
ShopsExplorer explorer = Session["Explorer"] as ShopsExplorer;
if (explorer != null)
{
// ho recuperato uno ShopsExplorer in sessione
// verifico lo stato
if (!explorer.IsCompleted)
{
// mostro il messaggio di attesa
// ...
}
else
{
// il lavoro è terminato, mostro i risultati
// ...
}
}
}
}
Se il processo di interrogazione non è ancora terminato, viene visualizzata l'apposita View, forzando un refresh della pagina dopo un certo periodo di tempo:
if (!explorer.IsCompleted)
{
// mostro il messaggio di attesa
mvResults.ActiveViewIndex = 0;
// imposto il refresh automatico della pagina tra 2 secondi
this.Response.Write("<meta http-equiv=\"refresh\" content=\"2\">");
}
else
{
// ...
}
Quando invece IsCompleted vale true, possono finalmente essere mostrati i risultati:
if (!explorer.IsCompleted)
{
// ...
}
else
{
// il lavoro è terminato, mostro i risultati
mvResults.ActiveViewIndex = 1;
lblTime.Text = string.Format("Inizio: {0:T} - Fine: {1:T} - Totale: {2}", explorer.StartTime, explorer.EndTime, explorer.EndTime.Subtract(explorer.StartTime));
gridResults.DataSource = explorer.Results;
gridResults.DataBind();
}
Esecuzione parallela e sincronizzazione di thread
L'esperienza dell'utente è già migliorata di molto rispetto al caso precedente, ma c'è ancora un aspetto su cui si può intervenire per sfruttare al meglio le potenzialità che il multi-threading offre. Infatti, se si analizza il codice del metodo ShopsExplorer.GetPrices(), si può notare che tutte le invocazioni sono eseguite in sequenza, con il risultato che il tempo di attesa totale è pari alla somma delle durate delle singole chiamate ai web service. Queste ultime hanno la caratteristica di essere completamente slegate e indipendenti le une dalle altre; l'attesa quindi è determinata solo da un limite architetturale della nostra soluzione e non da una necessità intrinseca. L'idea, allora, è quella di assegnare ognuna delle invocazioni ad un ulteriore thread, in modo che si possa parallelizzarne l'esecuzione, diminuendo di molto la durata dell'intero processo. C'è però una complicazione costituita dal fatto che il metodo IBookstoreProvider.GetPrice() restituisce un'istanza di PriceResult.
PriceResult GetPrice(string isbn);
Un thread può eseguire solo metodi con una delle seguenti signature, identificate dai delegate ThreadStart o ParametrizedThreadStart:
void MethodName(); // delegate ThreadStart
void MethodName(object parameter); // delegate ParametrizedThreadStart Pertanto IBookstoreProvider.GetPrice() non può essere invocato direttamente e da qui nasce la necessità di un metodo intermedio doProcess che, rispettando i requisiti di forma imposti dalla classe Thread, si occupi di eseguire l'interrogazione al provider. Di conseguenza l'oggetto MultiThreadedShopsExplorer ha un'implementazione simile alla seguente:
public void GetPrices()
{
isCompleted = false;
startTime = DateTime.Now;
try
{
// altro codice qui
foreach (IBookstoreProvider provider in getAvailableProviders())
{
Thread thread = new Thread(new ParameterizedThreadStart(doProcess));
// altro codice qui
thread.Start(provider);
}
// altro codice qui
}
finally
{
isCompleted = true;
endTime = DateTime.Now;
}
}
private void doProcess(object parameter)
{
IBookstoreProvider provider = parameter as IBookstoreProvider;
if (provider != null)
{
results.Add(provider.GetPrice(isbn));
}
}
4 pagine in totale: <<Indietro 1 [2] 3 4 Avanti >>
Attenzione: Questo articolo contiene un allegato
Contenuti dell'articolo
Per inserire un commento, devi registrarti alla nostra community.







Difficoltà
Stampa
Download



