Pagina personale di:
Carlo Vecchio
appunti di C#, R, SQL Server, ASP.NET, algoritmi, numeri
Vai ai contenuti

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.

Esempio 2: Task asincroni
  • 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.


© 2022 Carlo Vecchio
Torna ai contenuti