Esercizio 1: Creazione Base e Sincronizzazione con `Join`
Obiettivo: Comprendere come creare, avviare e attendere la terminazione di un thread.
Richieste:
Scrivere un programma che crea due thread
• Il primo thread deve eseguire un metodo che stampa i numeri da 1 a 5, con una pausa di 1 secondo tra ogni numero.
• Il secondo thread deve eseguire un metodo che stampa le lettere dalla 'A' alla 'E', con una pausa di 500 millisecondi tra ogni lettera.
• Il thread principale (`Main`) deve avviare entrambi i thread e poi attendere che entrambi abbiano terminato il loro lavoro prima di stampare un messaggio finale ("Lavoro completato.") e chiudersi.
• Utilizza il metodo `Join()` per garantire che il `Main` aspetti correttamente.

Esercizio 2: Monitoraggio dello Stato di un Thread
Obiettivo: Imparare a controllare lo stato di un thread durante la sua esecuzione.
Richieste:
Scrivere un programma che crea tre thread
• Assegni un nome ad ogni thread (es. Primo, Secondo, Terzo)
• Ogni thread stampa il proprio nome ed esegue un'operazione lunga che dura 10 secondi.
• Il main stampa lo stato di ogni thread e
o se non è "vivo" lo avvia thread.
o Attende 3 secondo.
o Stampa di nuovo lo stato di ogni thread e se è "vivo"
? Usa `Join()` per attendere la sua terminazione.
• Stampa per l'ultima volta lo stato dei thread.
• Analizza e commenta nel codice l'output che ti aspetti di vedere per ogni `Conso-le.WriteLine`.

Esercizio 3: Foreground vs. Background
Obiettivo: Comprendere la differenza fondamentale tra thread in foreground e in back-ground e il loro impatto sulla terminazione dell'applicazione.
Richieste:
Crea due thread, entrambi con un ciclo di lavoro che dura 5 secondi.
• Imposta il primo thread come background.
• Lascia il secondo thread come foreground.
• Il thread principale (`Main`) deve avviare entrambi i thread e poi stampare un mes-saggio ("Il Main ha finito, l'applicazione si chiuderà?") e terminare immediatamente, senza usare `Join()`.
• Esegui il programma e osserva il comportamento. Spiega perché l'applicazione rimane aperta e cosa viene stampato a schermo.
• Modifica: Commenta la riga che avvia il thread in foreground. Esegui di nuovo il pro-gramma e descrivi la differenza nel comportamento di chiusura dell'applicazione.
Esercizio 4: Combinazione di Concetti (Manager e Operaio)
Obiettivo: Simulare uno scenario più complesso in cui un thread "manager" monitora un thread "operaio".
Richieste:
1. Creare un thread operaio (in foreground) che simuli un lavoro di 10 secondi.
2. Creare un thread supervisore (in background) che, ogni 2 secondi, controlli lo stato del thread operaio e stampi un messaggio a console, come: `"[Supervisore]: Lo stato dell'operaio è: Running"`.
3. Il thread principale (`Main`) ha il ruolo di "manager":
• Avvia sia il thread operaio che quello supervisore.
• Attende la terminazione solo del thread operaio usando `Join()`.
• Una volta che l'operaio ha finito, il `Main` stampa un messaggio finale ("L'operaio ha finito, il manager chiude la giornata.") e termina.
4. Spiega perché il thread supervisore si arresta automaticamente senza bisogno di un `Join()` specifico.