CodeGym /Corsi /C# SELF /Avviare thread usando la classe

Avviare thread usando la classe Thread

C# SELF
Livello 55 , Lezione 1
Disponibile

1. Introduzione

Se pensi al processo come a un supermercato, i thread (thread) sono le cassiere alle diverse casse che servono i clienti contemporaneamente. Tutte le cassiere lavorano nello stesso negozio, ma ognuna esegue il proprio compito in parallelo, quindi il lavoro procede più velocemente ed efficacemente. I thread all'interno di un processo permettono di eseguire più compiti nello stesso momento, condividendo risorse comuni e coordinando il lavoro dentro la stessa applicazione. Il vantaggio principale è che i thread possono davvero lavorare in parallelo, se la CPU supporta il multitasking.

Perché serve il multithreading nella pratica

Perché vorremmo lanciare più thread? Ecco un paio di situazioni reali:

  • Stai scrivendo un'app con interfaccia grafica e non vuoi che si "blocchi" durante un'operazione lunga.
  • Devi scaricare più file contemporaneamente.
  • In un gioco i nemici devono “pensare” indipendentemente l'uno dall'altro.

È sorprendente, ma in molti programmi si trovano ancora blocchi dovuti a un uso inesperto dei thread. Oggi impariamo a evitare questi pasticci.

La classe Thread: la base del multithreading manuale

La classe Thread è un dinosauro nel panorama del multithreading in .NET. Nonostante siano arrivati strumenti più moderni (Task, async/await), lavorare con Thread ha ancora senso, specialmente se vuoi sentire la gestione dei thread "dall'inizio".

Schema per creare un thread

  1. Creare un oggetto della classe Thread, passando il metodo da eseguire nel thread.
  2. Avviare il thread con il metodo Start().
  3. (Opzionale) Controllare cosa succede — tutto potrebbe funzionare in parallelo!

2. Avviare un thread con Thread

Proviamo a sentire sulla pratica la magia del parallelismo. Aggiungiamo alla nostra app un piccolo classe che conta fino a un certo numero e stampa il progresso. Impareremo ad avviare questo lavoro in un thread separato.

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Console.WriteLine("Il thread principale è partito!");

        // Creiamo l'oggetto thread indicando il metodo da eseguire
        Thread workerThread = new Thread(CountToTen);

        // Avviamo il nuovo thread
        workerThread.Start();

        // Anche il thread principale fa qualcosa: stampa punti...
        for (int i = 0; i < 5; i++)
        {
            Console.Write(".");
            Thread.Sleep(500); // Ritardo per chiarezza
        }

        Console.WriteLine("\nIl thread principale è terminato!");
    }

    static void CountToTen()
    {
        for (int i = 1; i <= 10; i++)
        {
            Console.WriteLine($"[Thread] Contiamo: {i}");
            Thread.Sleep(400);
        }
        Console.WriteLine("[Thread] Fatto!");
    }
}

Cosa succede?

Vedrai nella console che i punti e "Contiamo: X" appaiono mescolati. Questo è il primo segno del multithreading! Il thread principale stampa i suoi punti e il nuovo thread conta fino a 10. Non si disturbano a vicenda, come due musicisti in una band: uno suona la batteria, l'altro il pianoforte. Ognuno suona la sua parte e insieme viene fuori musica.

3. Come passare dati al thread?

A volte il thread deve sapere non solo cosa fare, ma anche su cosa lavorare. Se il metodo del thread prende parametri, come li passiamo?

Opzione 1: usare una lambda (metodo anonimo)

int bounds = 7;
Thread t = new Thread(() => CountToNumber(bounds));
t.Start();
static void CountToNumber(int n)
{
    for (int i = 1; i <= n; i++)
    {
        Console.WriteLine($"[Thread] {i} / {n}");
        Thread.Sleep(300);
    }
}

Qui avvolgiamo la chiamata al metodo desiderato in una lambda per passare i parametri. Questa è una pratica molto comune, perché Thread si aspetta un metodo senza parametri (ThreadStart).

Opzione 2: usare ParameterizedThreadStart

Si può usare il delegato speciale ParameterizedThreadStart, che accetta un parametro di tipo object.

Thread t = new Thread(CountToNumberObject);
t.Start(12);

static void CountToNumberObject(object? n)
{
    int max = (int)n!;
    for (int i = 1; i <= max; i++)
    {
        Console.WriteLine($"[Thread] {i} / {max}");
        Thread.Sleep(200);
    }
}

Sì, il tipo del parametro è object, quindi serve il cast. Non è il massimo, ma funziona! Tuttavia il C# moderno preferisce la variante con lambda.

4. Gestire la vita del thread

Vediamo quali strumenti utili fornisce la classe Thread.

Proprietà / Metodo Scopo
Thread.Start()
Avvia il thread (il metodo specificato alla creazione)
Thread.Join()
Attende la terminazione del thread (blocca il thread chiamante fino al termine dell'altro thread)
Thread.IsAlive
Mostra se il thread è in esecuzione ora (true/false)
Thread.Name
Permette di dare un nome al thread (utile per il debugging)
Thread.CurrentThread
Ottieni l'oggetto che rappresenta il thread corrente
Thread.Sleep(ms)
Ferma il thread corrente per ms millisecondi

Esempio: aspettiamo la terminazione di un thread

Talvolta è necessario che il thread principale aspetti la fine di un thread secondario.

Thread t = new Thread(() =>
{
    for (int i = 0; i < 5; i++)
    {
        Console.WriteLine($"[Secondo thread] {i}");
        Thread.Sleep(300);
    }
});
t.Start();

Console.WriteLine("[Thread principale] Aspettiamo la fine del secondo thread...");
t.Join(); // Il thread principale aspetta qui

Console.WriteLine("[Thread principale] Il secondo thread è terminato!");

Senza Join() il programma potrebbe finire anche se il thread è ancora in esecuzione. Con Join() il thread principale aspetterà pazientemente che tutto sia concluso.

5. Sfumature utili

Denominare i thread: per non confondersi

Per il debugging puoi dare un nome ai thread:

Thread t = new Thread(() =>
{
    Console.WriteLine($"Questo viene eseguito nel thread: {Thread.CurrentThread.Name}");
});
t.Name = "Thread-Contatore";
t.Start();

Questo aiuta quando i thread sono molti e ognuno fa la sua cosa.

Limitazioni e futuro reale

È importante dire subito: nelle applicazioni moderne il controllo manuale dei thread tramite Thread è raro. Nella pratica si usano strumenti più potenti e intelligenti (Task, async/await), che vedremo più avanti. Ma capire le basi del funzionamento dei thread è importante per:

  • Capire il "dietro le quinte" di C# e .NET.
  • Colloqui tecnici (a volte ti chiedono la differenza tra Thread e Task).
  • Diagnosticare e risolvere problemi complessi in grandi applicazioni ereditate.

Schema riassuntivo: ciclo di vita di un thread

stateDiagram-v2
    [*] --> New: Thread creato
    New --> Running: Start()
    Running --> Stopped: Metodo terminato
    Stopped --> [*]

Ora sapete creare e avviare thread in C#. Non siete più solo passeggeri sul treno, siete il macchinista che controlla più carrozze! Davanti a voi: ciclo di vita del thread, la sua gestione, sincronizzazione e nuovi orizzonti del parallelismo.

6. Errori tipici e trucchi quando si lavora con Thread

Errore n.1: non avviare il thread.
Spesso si crea l'oggetto Thread ma si dimentica di chiamare Start(). Di conseguenza il thread non parte, e capire il motivo non è immediato.

Errore n.2: modificare dati condivisi senza sincronizzazione.
Se più thread lavorano sulla stessa variabile senza protezione, aspettati problemi! È come se due cassieri dessero il resto dalla stessa scatola — presto arriva il caos e gli errori.

Errore n.3: usare metodi deprecati e pericolosi.
Non usare Thread.Suspend(), Thread.Resume() e simili — sono considerati pericolosi e deprecati. Gestisci il ciclo di vita dei thread in altri modi.

Errore n.4: eccezioni non gestite dentro i thread.
Se un'eccezione avviene in un thread e non viene catturata, il thread terminerà e il thread principale potrebbe non accorgersene! Avvolgi il codice del thread in un blocco try-catch per intercettare e loggare gli errori.

Thread t = new Thread(() =>
{
    try
    {
        // ... il vostro codice
    }
    catch (Exception ex)
    {
        // Logghiamo l'errore
        Console.WriteLine($"[Thread] Errore: {ex.Message}");
    }
});
t.Start();
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION