dato che i "Tasks" ci semplificano le cose, conviene senza dubbio puntare su di loro,
di per se non hanno nulla di particolare
https://msdn.microsoft.com/en-us/lib...v=vs.110).aspx
ammettiamo che tu abbia una banale funzione sincrona
quando premi il pulsante esegui un metodo
codice:
private int m_Count = 0;
/// <summary>
/// Metodo sincrono controllato dal Main Thread
/// </summary>
private void ButtonUno_Click(object sender, RoutedEventArgs e)
{
this.SyncAction(10);
// stampiamo a schermo
lbText.Text = m_Count.ToString(); // out: 135
m_Count = 0;
}
/// <summary>
/// Metodo sincrono controllato dal Main Thread
/// </summary>
private void SyncAction(int n)
{
for (int i = 0; i < n; i++)
{
Thread.Sleep(100);
m_Count += i * 3;
}
}
però il metodo risulta lento, a causa della moltitudine di operazioni da effettuare pertanto questa azione porta al blocco dell'applicativo (il classico "non risponde" quando cerchi di interagire), questo perché l'unico Thread è quello del Main, il quale controllo anche i componenti grafici.
per ovviare a questa problematica puoi utilizzare un "Task", questo porta la tua azione ad essere gestita in modo asincrono.
ora sappiamo che la nostra variabile condivisa m_Count grazie all'utilizzo dei "Task" rimane protetta, però devi stare comunque attento alla condivisione dei componenti grafici.
ATTENZIONE QUESTA PARTE È DI PROPOSITO SBAGLIATA.
codice:
private int m_Count = 0;
/// <summary>
/// Metodo sincrono controllato dal Main Thread
/// </summary>
private void ButtonUno_Click(object sender, RoutedEventArgs e)
{
Task.Run(() => this.AsyncAction(10));
// stampiamo a schermo
lbText.Text = m_Count.ToString(); // out: 0 ~ 18 ~ 9 ~ 135 ~ ...
m_Count = 0;
}
/// <summary>
/// Metodo asincrono controllato dal Task
/// </summary>
private void AsyncAction(int n)
{
for (int i = 0; i < n; i++)
{
Thread.Sleep(100);
m_Count += i * 3;
}
}
abbiamo generato un problema di sincronizzazione, questo perché il Thread Main continua il suo percorso, e in concorrenza il Task esegue le proprie azioni, dato che è il sistema operativo che ne decide l'esecuzione non puoi sapere se il Task finirà prima la propria esecuzione rispetto al Thread Main, quindi
lbText.Text = m_Count.ToString(); // out: 0 ~ 18 ~ 9 ~ 135 ~ ...
ti da un output sempre differente a seconda di come il sistema operativo gestisce il tutto.
per ovviare a questo potresti gestire l'output a video direttamente nell'azione del tuo task
codice:
/// <summary>
/// Metodo asincrono controllato dal Task
/// </summary>
private void AsyncAction(int n)
{
for (int i = 0; i < n; i++)
m_Count += this.ExternalAction(i);
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() =>
{
// stampiamo a schermo
lbText.Text = m_Count.ToString(); // out: 135
m_Count = 0;
}));
}
i controlli grafici possono essere gestiti solo dal Thread in cui sono stati creati, per tale motivo devi passare attraverso il Dispatcher dell'applicazione per poterli manipolare, il quale rallenta l'esecuzione del Task.
potresti anche attendere il termine di esecuzione del Task prima di continuare con il Thread Main
codice:
/// <summary>
/// Metodo sincrono controllato dal Main Thread
/// </summary>
private void ButtonUno_Click(object sender, RoutedEventArgs e)
{
Task t = Task.Run(() => this.AsyncAction(10));
t.Wait(); // attendiamo il termine del task.
// stampiamo a schermo
lbText.Text = m_Count.ToString(); // out: 135
m_Count = 0;
}
il problema che per aspettare il Task, il Main Thread viene bloccato, quindi torni ad avere il problema della soluzione sincrona.
per questo genere di problematiche entrano in aiuto le parole chiave ASYNC e AWAIT
https://msdn.microsoft.com/en-us/library/hh191443.aspx
Grazie alla parola chiave async, posta nell'intestazione del metodo, definisci che questa procedura verrà eseguita in un contesto di sincronizzazione differente, sempre appartenente al Thread Main, ma non bloccante.
Tramite la parola await, posta di fronte alla nostra procedura awaitable dettata dal Task, definiamo che necessitiamo di aspettare il termine del Task prima di continuare ad eseguire le nostre azioni nel contesto di sincronizzazione.
codice:
/// <summary>
/// Metodo sincrono controllato dal Main Thread in un contesto differente di sincronizzazione
/// </summary>
private async void ButtonUno_Click(object sender, RoutedEventArgs e)
{
await Task.Run(() => this.AsyncAction(10)); // attende fino al termine della procedura, il ritorno void
// stampiamo a schermo
lbText.Text = m_Count.ToString(); // out: 135
m_Count = 0;
}
Il ritorno dato dal Task dipende dal ritorno della tua funzione asincrona, fino ad ora abbiamo utilizzato un metodo void, ma se non volessimo più appoggiarci ad una variabile condivisa ma lasciare la procedura confinata a thread basta generare un ritorno classico da una funzione, e leggerlo all'uscita del Task, grazie alla parola chiave await, l'esecuzione attenderà il ritorno e quindi l'assegnazione ad m_Count prima di procedere con il resto del metodo.
codice:
/// <summary>
/// Metodo sincrono controllato dal Main Thread in un contesto differente di sincronizzazione
/// </summary>
private async void ButtonUno_Click(object sender, RoutedEventArgs e)
{
m_Count = await Task.Run(() => this.AsyncAction(10)); // attende fino al termine della procedura, il ritorno int
// stampiamo a schermo
lbText.Text = m_Count.ToString(); // out: 135
m_Count = 0;
}
/// <summary>
/// Metodo asincrono controllato dal Task
/// </summary>
private int AsyncAction(int n)
{
int count = 0;
for (int i = 0; i < n; i++)
{
Thread.Sleep(100);
count += i * 3;
}
return count;
}
ricordati che per usare await il metodo deve essere marcato async, e per ottenere i benefici dell'async all'interno del metodo deve esserci una funzione awaitable, in caso contrario l'esecuzione avverrà in modo sincrono.
Ormai nel framework 4.5 trovi parecchie tra funzioni e metodi che ti richiedono questo genere d'utilizzo, notoriamente lente come DB, web, e così via.
Se vuoi applicare questo genere di utilizzo sempre, senza far scegliere all'utente se eseguirlo in maniera sincrona o asincrona ti basta eseguire il Task nel metodo specifico, e dare come ritorno "Task<T>", oppure se void "Task".
codice:
/// <summary>
/// Metodo sincrono controllato dal Main Thread
/// </summary>
private async void ButtonUno_Click(object sender, RoutedEventArgs e)
{
m_Count = await this.AsyncAction(10);
// stampiamo a schermo
lbText.Text = m_Count.ToString();
m_Count = 0;
}
/// <summary>
/// Metodo asincrono controllato dal Main Thread, con all'interno un lambda controllato dal Task
/// </summary>
private Task<int> AsyncAction(int n)
{
return Task.Run(() => // metodo lambda controllato dal Task
{
int count = 0;
int[] results = new int[n];
for (int i = 0; i < n; i++)
{
Thread.Sleep(100);
count += i * 3;
}
return count;
});
}
in questo caso il ritorno è un Task di tipo int, questo significa che applicando la parola await al richiamo ci verrà consegnato al suo termine un risultato futuro di tipo int.
dato che parlavi di un esecuzione continua, per non intasare eccessivamente il Pool, puoi marcare il tuo Task come LongRunning, questo verrà configurato in modo più appropriato.
Per questioni di User Experience se un azione dura a lungo, è fondamentale poterla interromperla, per tale motivo puoi definire un Token necessario alla cancellazione del Task.
codice:
CancellationTokenSource m_CancellationToken;
/// <summary>
/// Metodo sincrono controllato dal Main Thread
/// </summary>
private async void ButtonUno_Click(object sender, RoutedEventArgs e)
{
m_CancellationToken = new CancellationTokenSource();
Task<int> task = new Task<int>(
() => AsyncAction(10, m_CancellationToken.Token), // azione asincrona
m_CancellationToken.Token, // token per la cancellazione
TaskCreationOptions.LongRunning); // definiamo un procedimento che durerà parecchio
task.Start(); // lanciamo il task
m_Count = await task; // attendiamo il ritorno
// stampiamo a schermo
lbText.Text = m_Count.ToString();
m_Count = 0;
}
/// <summary>
/// Metodo asincrono controllato dal Task
/// </summary>
private int AsyncAction(int n, CancellationToken token)
{
int count = 0;
for (int i = 0; i < n; i++)
{
// se è stata richiesta un interruzione ci fermiamo
// e torniamo il risultato calcola solo sino ad ora.
if (token.IsCancellationRequested)
break;
Thread.Sleep(1000);
count += i * 3;
}
return count;
}
/// <summary>
/// Metodo sincrono controllato dal Main Thread
/// </summary>
private void ButtonDue_Click(object sender, RoutedEventArgs e)
{
m_CancellationToken.Cancel(true); // richiediamo l'interruzione.
}
}