PROGRAMMAZIONE ASINCRONA

C sharpNella programmazione asincrona in c sharp esistono metodi sincroni e metodi asincroni. Cerchiamo di capire prima di tutto quando eseguire operazioni in modo asincrono e quando no.

  • In linea generale possiamo dire che dobbiamo ricorrere alle operazioni asincrone quando dobbiamo accedere a periferiche di I/O come ad esempio leggere dal disco o lavorare con la rete. Collegarsi e inviare query a un database è un’operazione asincrona, leggere un file di testo di grandi dimensioni, inviare una richiesta a un web service, inviare una mail sono tutte operazioni asincrone.
  • Viceversa non si hanno benefici quando un metodo fa intenso uso della CPU e sta già ottimizzando il thread corrente. Calcolare ad esempio le cifre del PI greco, ordinare gli oggetti di una List, ridimensionare un’immagine sono tutte operazioni che fanno intenso uso della CPU e tengono già impegnato attivamente il thread.

Ci sono operazioni che non impegnano intensivamente il thread che rimane così in attesa, ad esempio quando si invia una query ad un database è il database che lavora se il thread dovesse aspettare i risultati dal database si introdurrebbe una inefficienza, se invece usiamo le operazioni asincrone il thread può svincolarsi e servire altre richieste come ad esempio in una web application.

LE KEYWORD AWAIT E ASYNC

Per marcare un metodo come asincrono dobbiamo usare la keyword await prima dell’invocazione del metodo asincrono. Tale keyword designa l’operazione come asincrona quindi occorre aspettare il suo completamento prima che sia elaborata l’istruzione successiva ad await. Insieme ad await va usata le keyword async nella firma del metodo. Usando la keyword await un thread si renderà disponibile per eseguire altro lavoro. Un task è una classe che serve a descrivere un’operazione asincrona. Se il metodo restituisce void allora si usa Task altrimenti Task. I vantaggi della programmazione asincrona si notano soprattutto nell’ambito di applicazioni WPF, dato che in questi casi il thread chiamante è quasi sempre quello dell’interfaccia utente. Sono metodi che durano a lungo e bloccano l’interfaccia e quindi l’applicazione non è più responsiva. Ciò non accade nel caso di esecuzione asincrona. Anche in altri ambiti, quali per esempio quello delle applicazioni web, lo sviluppo asincrono presenta immensi vantaggi, perché consente in ogni caso di liberare un thread che può essere sfruttato dal server per servire un’altra richiesta.

Programmazione asincrona
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

namespace ProgrammazioneAsincrona
{
    class Program
    {
        static async Task Main(string[] args)
        {
            int result = await DownloadAsync();
            Console.WriteLine($"Lunghezza stringa: {result}");
            IndependentWork();
            await ParallelMethod();
        }
        //METODO ASINCRONO
        private static async Task<int> DownloadAsync()
        {
            /*Metodo asincrono. In condizioni normali, dovremmo in qualche modo gestire il callback su questo oggetto 
            per recuperare l’informazione prelevata dalla rete; grazie alla parola chiave await, 
            invece, è il compilatore stesso a preoccuparsi di iniettare tutto il codice necessario, 
            e noi possiamo semplicemente limitarci a prelevare il risultato dell’esecuzione asincrona.
            Il Thread non resta bloccato.*/
            Console.WriteLine("Async Download");
            WebRequest client = HttpWebRequest.Create("https://www.google.com");
            var response = client.GetResponse();
            using(var reader = new StreamReader(response.GetResponseStream()))
            {
                var result = await reader.ReadToEndAsync();
                return result.Length;
            }
        }
        //METODO SINCRONO
        private static int Download()
        {
            /*--Metodo sincrono. Fintanto che il risultato non viene recuperato, il thread resta bloccato e in attesa.*/
            Console.WriteLine("Download");
            WebRequest client = HttpWebRequest.Create("https://www.google.com");
            var response = client.GetResponse();
            using(var reader = new StreamReader(response.GetResponseStream()))
            {
                var result = reader.ReadToEnd();
                Console.WriteLine(result);
                return result.Length;
            }
        }
        private static void IndependentWork() => Console.WriteLine("Meotodo IndependentWork()....");
        private static async Task ParallelMethod()
        {
            /*In questo caso, abbiamo evitato di utilizzare await in corrispondenza di 
            ogni singola chiamata, memorizzando le istanze dei Task<string> restituite 
            all’interno della lista tasks. Il risultato è che il flusso del codice non 
            viene più interrotto per attendere i risultati e le operazioni vengono effettivamente 
            avviate in parallelo. Per recuperare poi i risultati, dobbiamo attendere 
            il completamento di tutte le chiamate asincrone, effettuando un await sul metodo
            WhenAny della classe, come mostrato nel codice.*/
            Console.WriteLine("Parallel operations");
            HttpClient client = new HttpClient();
            Task<string> task1 = client.GetStringAsync("http://www.google.com");
            Task<string> task2 = client.GetStringAsync("http://www.bing.com");
            Task<string> task3 = client.GetStringAsync("http://www.yhaoo.com");
            var tasks = new List<Task> { task1, task2, task3 };
            while (tasks.Count > 0)
            {
                Task finishedTask = await Task.WhenAny(tasks);
                if (finishedTask == task1)
                {
                    Console.WriteLine($"Google numero caratteri nella pagina: {task1.Result.Length}");
                }
                else if (finishedTask == task2)
                {
                    Console.WriteLine($"Bing numero caratteri nella pagina: {task2.Result.Length}");
                }
                else if (finishedTask == task3)
                {
                    Console.WriteLine($"Yhaoo numero caratteri nella pagina: {task3.Result.Length}");
                }
                tasks.Remove(finishedTask);
            }
        }  
    }
}

APPROFONDIMENTO

La programmazione asincrona in C# è una tecnica che permette di eseguire operazioni in background senza bloccare il thread principale dell’applicazione. Questo è particolarmente utile per operazioni di I/O, come chiamate a database, letture/scritture su file e richieste di rete, che possono essere lente.

C# offre diverse funzionalità per implementare la programmazione asincrona. Ecco una panoramica di alcune delle tecniche più comuni:

1. Utilizzo di async e await

Il modo più comune per scrivere codice asincrono in C# è usare le parole chiave async e await. Ecco un esempio di base:

using System;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
     static async Task Main(string[] args)
     {
        string url = “https://example.com“;
        string content = await DownloadContentAsync(url);
        Console.WriteLine(content);
     }

     static async Task<string> DownloadContentAsync(string url)
     {
         using (HttpClient client = new HttpClient())
         {
             return await client.GetStringAsync(url);
         }
     }
}

In questo esempio, DownloadContentAsync è un metodo asincrono che utilizza HttpClient per scaricare il contenuto di una pagina web. La parola chiave await fa sì che il metodo attenda il completamento dell’operazione asincrona senza bloccare il thread principale.

2. Task Parallel Library (TPL)

La TPL è una libreria che fornisce una serie di tipi e metodi per eseguire attività in parallelo. Ecco un esempio di base:

using System;
using System.Threading.Tasks;

class Program
{
    static void Main(string[] args)
    {
       Task.Run(() => DoWork());
       Console.WriteLine(“Work started“);
       Console.ReadLine();
    }

    static void DoWork()
    {
       Task.Delay(2000).Wait(); // Simula un lavoro che richiede tempo
       Console.WriteLine(“Work completed“);
    }
}

In questo esempio, Task.Run esegue DoWork su un thread separato, consentendo al metodo Main di continuare l’esecuzione senza attendere il completamento di DoWork.

3. Eseguire più operazioni in parallelo

È possibile eseguire più operazioni asincrone in parallelo usando Task.WhenAll o Task.WhenAny.

using System;
using System.Threading.Tasks;

class Program
{
     static async Task Main(string[] args)
     {
        Task task1 = Task.Delay(2000);
        Task task2 = Task.Delay(3000);

        await Task.WhenAll(task1, task2);

        Console.WriteLine(“Both tasks completed“);
     }
}

LINK AI PRECEDENTI POST

LINK AL CODICE SU GITHUB