C# - Programmazione asincrona
C#
Programmazione asincrona (async, await)
- La programmazione asincrona consente al programmatore di gestire l'esecuzione di sotto-processi in parallelo.
- È utilizzata tipicamente in operazioni di lettura o scrittura dati che possono richiedere tempi più o meno lunghi e questo renderebbe l'interfaccia 'not-responsive'.
- L'esempio 1 fa uso di programmazione sincrona.
- L'esempio 2 fa invece uso di programmazione asincrona e ne migliora il tempo di esecuzione a scapito di un peggioramento della leggibilità. In esso infatti vengono introdotte le parole chiave async e await.
Esempio 1: Processi sincroni
- Applicazione Console.
- In questo esempio i Processi Process1(), Process2() e Process3() sono eseguiti in modalità sincrona. Essi sono eseguiti in modo sequenziale: quando termina il primo Processo, viene eseguito il secondo e quando termina il secondo Processo, viene eseguito il terzo.
- Ogni Processo esegue un ciclo di 5 passi della durata rispettivamente di 100 ms, 100 ms e 50 ms.
using System.Threading.Tasks;
static void Main(string[] args)
{
Console.WriteLine("Start Process p1, p2 and p3.");
Process1();
Process2();
Process3();
Console.WriteLine("All Processes finished.");
}
private static void Process1()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine(String.Format($"Process 1 - Step {i}"));
Task.Delay(100).Wait();
}
}
private static void Process2()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine(String.Format($"Process 2 - Step {i}"));
Task.Delay(100).Wait();
}
}
private static void Process3()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine(String.Format($"Process 3 - Step {i}"));
Task.Delay(50).Wait();
}
}
- L'output è il seguente. Come si vede i Processi sono eseguiti in modo sequenziale.
Start Process p1, p2 and p3.
Process 1 - Step 0
Process 1 - Step 1
Process 1 - Step 2
Process 1 - Step 3
Process 1 - Step 4
Process 2 - Step 0
Process 2 - Step 1
Process 2 - Step 2
Process 2 - Step 3
Process 2 - Step 4
Process 3 - Step 0
Process 3 - Step 1
Process 3 - Step 2
Process 3 - Step 3
Process 3 - Step 4
All Processes finished.
- Applicazione Console.
- In questo esempio si è convertito l'esempio 1 in modalità asincrona. I Processi Process1(), Process2() e Process3() sono diventati Task.
- Ogni Task esegue un ciclo di 5 passi della durata rispettivamente di 100 ms, 100 ms e 50 ms.
- In pratica per trasformare un programma da sincrono ad asincrono, occorre fare i seguenti passi.
- Trasformare tutti i sotto Processi in Task asincroni. Restituiscono un oggetto di tipo Task<T>.
- Trasformare il Processo principale (il Main) in Task asincrono. Restituisce un oggetto di tipo Task.
- Nel Processo principale, aggiungere le istruzioni atte al controllo dei sotto Processi (WhenAny e WhenAll).
- Ricordarsi che async e await vanno usate in coppia; ricordarsi anche che await restituisce il controllo al chiamante.
using System.Collections.Generic;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("Start Tasks p1, p2 and p3.");
Task<P1> p1 = Process1();
Task<P2> p2 = Process2();
Task<P3> p3 = Process3();
var allTasks = new List<Task> { p1, p2, p3 };
while (allTasks.Count > 0)
{
Task finishedTask = await Task.WhenAny(allTasks);
if (finishedTask == p1)
{
Console.WriteLine("Task p1 finished.");
}
else if (finishedTask == p2)
{
Console.WriteLine("Task p2 finished.");
}
else if (finishedTask == p3)
{
Console.WriteLine("Task p3 finished.");
}
allTasks.Remove(finishedTask);
}
Console.WriteLine("All Tasks finished.");
}
private static async Task<P1> Process1()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine(String.Format($"Process 1 - Step {i}"));
await Task.Delay(100);
}
return new P1();
}
private static async Task<P2> Process2()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine(String.Format($"Process 2 - Step {i}"));
await Task.Delay(100);
}
return new P2();
}
private static async Task<P3> Process3()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine(String.Format($"Process 3 - Step {i}"));
await Task.Delay(50);
}
return new P3();
}
}
class P1
{ }
class P2
{ }
class P3
{ }
- I Processi (Task) sono lanciati all'inizio del Main().
- Ogni volta che un Task trova l'istruzione await, manda il controllo al chiamante; in questo modo i tre Task sono eseguiti contemporaneamente.
- Quando un Task termina, al compimento dei 5 cicli, restituisce un oggetto rispettivamente di tipo P1, P2 o P3.
- Il Main() una volta che ha lanciato i Task, procede con le altre istruzioni che preparano una lista di Task e ne controllano il termine dell'esecuzione.
- Questo controllo è fatto tramite il metodo WhenAny() che restituisce un Task appena termina la sua esecuzione.
- Il ciclo while continua finché ci sono oggetti nella lista 'allTasks' che contiene i Task in esecuzione.
- Quando tutti i Task sono terminati, si esce dal ciclo while e il programma termina.
- L'output è il seguente, ma potrà variare da esecuzione ad esecuzione in base ai tempi di esecuzione dei singoli Task in parallelo.
Start Tasks p1, p2 and p3.
Process 1 - Step 0
Process 2 - Step 0
Process 3 - Step 0
Process 3 - Step 1
Process 2 - Step 1
Process 1 - Step 1
Process 3 - Step 2
Process 3 - Step 3
Process 2 - Step 2
Process 1 - Step 2
Process 3 - Step 4
Task p3 finished.
Process 1 - Step 3
Process 2 - Step 3
Process 2 - Step 4
Process 1 - Step 4
Task p1 finished.
Task p2 finished.
All Tasks finished.