CodeGym /Corsi /C# SELF /Asincronicità vs. Multithreading (

Asincronicità vs. Multithreading ( async/await e Thread)

C# SELF
Livello 59 , Lezione 1
Disponibile

1. Introduzione

Nella programmazione i principianti (e anche sviluppatori esperti) spesso confondono due concetti simili ma in realtà diversi: multithreading e asincronicità. Ai colloqui amano porre questa domanda per vedere se la persona capisce la differenza, perché da questo dipende direttamente come scrivere codice veloce e reattivo.

Vediamo di capire qual è il trucco.

Multithreading: quando lavorano molte mani

Multithreading è l'organizzazione del lavoro del programma usando più thread. Un thread è un filo di esecuzione, una "pista" separata su cui la CPU esegue le istruzioni del programma. Un processo (per esempio la nostra applicazione .NET) può avviare più thread in modo che compiti diversi vengano eseguiti contemporaneamente.

Esempio dalla vita reale: Sei un project manager che assegna compiti diversi a colleghi diversi, e tutti questi compiti vengono svolti contemporaneamente. Per esempio, uno scrive il report, un altro chiama il cliente, il terzo prepara la presentazione.

Idea chiave del multithreading: i task avvengono realmente in parallelo (o quasi-parallelo, se c'è un solo processore, grazie al rapido context switching).

Asincronicità: saper non stare inattivi

Asincronicità è organizzare il codice in modo che il programma possa fare qualcos'altro mentre aspetta il completamento di un'operazione lunga (per esempio una risposta dalla rete o la lettura di un file). Il codice asincrono non necessariamente usa più thread! Semplicemente non blocca l'esecuzione del programma durante l'attesa di un'operazione.

Esempio dalla vita reale: invece di stare davanti alla macchina del caffè a guardare il caffè che scende, assegni il compito alla macchina e ti occupi di altro (rispondi alle email, leggi le notizie), e prendi il caffè quando è pronto.

Idea chiave dell'asincronicità: non stare inattivi mentre aspetti i compiti "lunghi", ma fare qualcosa di utile.

2. Qual è la differenza?

Molto spesso asincronicità e multithreading si usano insieme, e questo aumenta la confusione. Ma in realtà queste tecniche rispondono a domande diverse:

  • Multithreading serve per parallelizzare realmente il lavoro, sfruttando i processori/cores disponibili.
  • Asincronicità serve per gestire in modo intelligente l'attesa di risorse (I/O di rete, I/O su disco ecc.), senza bloccare thread.

Possono essere combinate, ma non sono obbligatoriamente correlate.

Breve visualizzazione

Multithreading (Threads) Asincronicità (Async)
Quando applicare? Quando il task "consuma" CPU (CPU-bound: calcoli, rendering, elaborazione di array) Quando il task "aspetta" un evento (I/O-bound: rete, disco, database)
Cosa fa il codice? Fa lavorare la CPU, avvia più thread, parallelizza realmente Quando aspetta i dati (è "inattivo"), libera il thread — quello può fare altro
Cosa gestisce? Numero di thread eseguiti contemporaneamente Chi è occupato/libero e cosa fare quando l'operazione finisce
Esempio tipico Compressione video, rendering immagine Download di file, richiesta HTTP al server

Esempio 1: Multithreading — calcoliamo velocemente

Supponiamo di avere un compito pesante: sommare numeri grandi.


void ComputeSum(long start, long end)
{
    long sum = 0;
    for (long i = start; i <= end; i++)
    {
        sum += i;
    }
    Console.WriteLine($"Somma da {start} a {end} = {sum}");
}

// Avviamo tre task contemporaneamente — ognuno calcola la sua parte
Thread t1 = new Thread(() => ComputeSum(1, 1000_000_000));
Thread t2 = new Thread(() => ComputeSum(1000_000_001, 2000_000_000));
Thread t3 = new Thread(() => ComputeSum(2000_000_001, 3000_000_000));

t1.Start();
t2.Start();
t3.Start();

// Aspettiamo il completamento di tutti i thread
t1.Join();
t2.Join();
t3.Join();

Perché qui ci servono i thread?
Perché è lavoro CPU-bound. I thread caricano realmente la CPU, e se hai una macchina multicore — il lavoro sarà più veloce.

Esempio 2: Asincronicità — aspettiamo la risposta del server

La rete è lenta, e mentre aspettiamo la risposta dal server il thread può essere "libero".


// Scarichiamo una pagina web in modo asincrono, il thread non viene bloccato
async Task DownloadPageAsync()
{
    using HttpClient client = new HttpClient();
    string html = await client.GetStringAsync("https://dotnet.microsoft.com/");
    Console.WriteLine(html.Length);
}

Perché qui serve asincronicità?
Diamo al sistema il comando "Inizia il download", e non blocchiamo il thread, aspettiamo la notifica quando i dati arrivano.

3. Asincronicità senza multithreading: mito o realtà?

Domanda: Qualsiasi codice asincrono avvia sempre un nuovo thread?
Risposta: No! Spesso l'asincronicità non richiede thread aggiuntivi.

Per esempio, quando fai await file.ReadAsync(...), .NET avvia l'operazione in modo asincrono a livello di OS, e il thread che ha fatto la chiamata diventa subito "libero" e ritorna al thread pool. Quando l'operazione finisce, nel pool verrà preso un thread libero che continuerà l'esecuzione del tuo task.

  • Se invece avessi usato la versione sincrona (file.Read(...)) — il thread aspetterebbe il completamento, senza fare niente.
  • Il codice asincrono dice: "Processore, mentre aspettiamo — occupati di altro!"

Illustrazione importante:


// Il "thread" non viene bloccato, aspetta solo che l'operazione sia pronta
await Task.Delay(1000); // Aspettare un secondo — non occupa la CPU!

Multithreading senza asincronicità

A volte ha senso parallelizzare il lavoro solo con thread: calcoli pesanti, grandi cicli di elaborazione dati ecc. In questo caso l'asincronicità è inutile per accelerare i calcoli in sé, perché la CPU sarà già al 100%.

Classico: elaborazione di file grandi


// Questo codice carica davvero la CPU — l'asincronicità non aiuta.
void CalculateHash(string file)
{
    byte[] data = File.ReadAllBytes(file); // sincrono!
    // Calcoliamo l'hash...
}

Vuoi accelerare — avvia più thread, ognuno lavora sul suo file.

4. Come appare questo nella tua applicazione?

Operazioni asincrone (await)

Nella nostra app didattica si può aggiungere il caricamento asincrono dei dati. Per esempio, quando richiedi i tassi di cambio o il meteo — è meglio farlo in modo asincrono.


async Task GetWeatherAsync(string city)
{
    using HttpClient client = new HttpClient();
    string json = await client.GetStringAsync($"https://api.weather.com/{city}");
    // Continuiamo il lavoro quando la risposta è arrivata
    Console.WriteLine($"Meteo a {city}: {json}");
}

Cosa succede sotto il cofano?

La chiamata await "taglia" il tuo metodo in due parti:

  • Hai avviato l'operazione asincrona — il thread OS viene "liberato" e può fare altri compiti.
  • Quando i dati sono pronti — l'esecuzione del metodo riprende su uno dei thread liberi del pool.

Multithreading per calcoli complessi

Nel nostro esempio con la calcolatrice (supponiamo che debba elaborare grandi array di dati) — qui ha senso avviare i calcoli in thread separati.


// Suddividiamo un grosso compito in parti più piccole, ognuna calcola la sua parte
List<Thread> threads = new List<Thread>();
for (int i = 0; i < 4; i++)
{
    int rangeStart = i * 1000000;
    int rangeEnd = (i + 1) * 1000000 - 1;
    Thread t = new Thread(() => ComputeSum(rangeStart, rangeEnd));
    threads.Add(t);
    t.Start();
}
// Aspettiamo il completamento di tutti i thread
foreach (Thread t in threads) t.Join();

5. Errori tipici e sfumature nel lavoro con l'asincronicità

Errore №1: usare async "per velocità".
Un errore molto comune è pensare che async renda il codice più veloce. Non è così. L'asincronicità riguarda la reattività, non la velocità di esecuzione.

Se il task è CPU-bound (carico sulla CPU) — l'asincronicità non lo renderà più veloce.
Se il task è I/O-bound (lavoro con rete, disco) — l'asincronicità è utile perché il thread non resta inattivo e può occuparsi di altro.

Errore №2: bloccare thread con Wait() e Result.
Nel codice asincrono non bisogna chiamare Wait() o la proprietà Result sulle task. Questo quasi sempre porta a blocchi di thread e deadlock.


// Male! Bloccherà il thread e causerà problemi
var result = GetDataAsync().Result;

async Task<string> GetDataAsync() { /* ... */ return "data"; }

Approccio corretto — usare await e non bloccare il thread.

Errore №3: asincronicità e UI.
Nelle applicazioni grafiche (WPF, WinForms) il problema principale è non congelare il UI-thread. Se nel thread principale avvii un'operazione lunga o bloccante, tutta l'app si "appende". L'asincronicità risolve questo: il lavoro pesante viene eseguito in background e l'interfaccia rimane reattiva.

Errore №4: mancanza di convenzione di naming per i metodi asincroni.
Se non aggiungi il suffisso Async ai metodi asincroni, è facile confondersi su quale metodo è sincrono e quale asincrono. Questo porta a blocchi accidentali e bug durante le chiamate. Nomina sempre i metodi asincroni con Async alla fine.

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION