I thread in C#, seconda puntata

Nella prima puntata abbiamo visto una piccola introduzione ai thread in C#. Con questo articolo andiamo un po’ più nel dettaglio, vedendo come si passa un parametro ad un worker method e la differenza tra thread in background e foreground.

ParameterizedThreadStart

Non ditemi che avete sempre scritto metodi che non accettano parametri perché non ci credo. Naturalmente si può passare un parametro ad un worker method, ma non possiamo fare semplicemente metodo(param1,…,paramN): dobbiamo servirci della classe ParameterizedThreadStart. Questa classe permette di passare un parametro (di tipi object) a ciascun thread, ed è utile quando si invoca lo stesso metodo su ciascun thread e questo metodo può lavorare su dati diversi. Un esempio:


using System;
using System.Net;
using System.Threading;

namespace EsempiThread
{
    public class Parametri : IEsempio
    {
        public void Run()
        {
            Thread t1 = new Thread(new ParameterizedThreadStart(WebDownload)),
                t2 = new Thread(new ParameterizedThreadStart(WebDownload)),
                t3 = new Thread(WebDownload);
            t1.Start("http://www.pierpaoloparis.it");
            t2.Start("https://www.google.it");
            t3.Start("https://msdn.microsoft.com/it-it/library/system.threading.parameterizedthreadstart(v=vs.110).aspx");
            t3.Join();
        }

        private void WebDownload(object parameter)
        {
            string url = (string)parameter;
            using (var w = new WebClient())
            {
                Console.WriteLine("Sto scaricando... " + url);
                string page = w.DownloadString(url);
                Console.WriteLine("Scaricato {0}, lunghezza {1}", url, page.Length);
            }
        }
    }
}

Per fortuna abbiamo dello zucchero sintattico. I più attenti di voi avranno notato che per t3 ho passato direttamente il worker method, senza fare ricorso a ParameterizedThreadStart: questo perché uno dei quattro costruttori di Thread accetta un ParameterizedThreadStart, e quando il worker method accetta un parametro il compilatore inserisce la chiamata a ParameterizedThreadStart dietro le quinte al posto nostro.

Background thread e foreground thread

La differenza tra foreground thread e background thread è una di quelle cose fondamentali da capire.

Spiegazione breve

Non affidate MAI un compito importante, e che deve terminare con un risultato, ad un thread in background.

Spiegazione dettagliata

I thread in foreground vengono usati per tenere l’applicazione in vita. Quando tutti i foreground thread terminano allora anche l’applicazione termina. I background thread vengono terminati, uccisi senza pietà, quando tutti i foreground thread terminano. Ora la spiegazione breve dovrebbe essere più chiara: non c’è nessuna garanzia che un thread in background termini il suo compito, quindi non è il caso di affidargli un compito fondamentale per l’applicazione. Il solito esempio:

using System;
using System.Threading;

namespace EsempiThread
{
    public class ForegroundBackgroundTerminaSubito : IEsempio
    {
        public void Run()
        {
            Thread bgThread = new Thread(BgWorker),fgThread = new Thread(FgWorker);
            bgThread.IsBackground = true;
            bgThread.Start();
            fgThread.Start();
            fgThread.Join();
        }    

        private void BgWorker()
        {
            Thread.Sleep(1000);
            for (int i = 0; i < 100; i++)
                Console.WriteLine("Nessuno vedrà questa scritta a video troppe volte, parola di BackgroundThread ({0})",i);
        }
        private void FgWorker()
        {
            Console.WriteLine("Io termino subito e faccio uno scherzone al background thread");
        }
    }
}

Nella costruzione di fgThread ho omesso di assegnare un valore alla proprietà IsBackground, perché questa è inizializzata di default a false, quindi se non si specifica diversamente un thread è un foreground thread. Se provate ad impostare IsBackground a false per fgThread vedrete che la scritta viene stampata a video 10 volte prima che il programma termini. C’è una eccezione: anche se abbiamo un background thread, se chiamiamo a Join() su di esso l’esecuzione non termina immediatamente, ma aspetta che il background thread termini. Quindi, a mio avviso, se non abbiamo un motivo veramente forte per chiamare la Join() su un background thread, facendolo quindi comportare come un foreground thread, può significare che abbiamo le idee parecchio confuse.

Quindi? Foreground o background?

La risposta non è un assoluto, ma dipende. Dipende da cosa fa il thread. Se fa una complicata elaborazione su dei dati bancari, che non posso in nessun modo lasciare a metà, allora un foreground thread è quello che ci serve. Se invece il thread deve fare una operazione sulla UI (ad esempio far girare un indicatore di avanzamento), o comunque un altro compito infinito e senza né risultato né side effect allora questo dovrà essere senz’altro un background thread (nessuno è contento di non poter chiudere correttamente un’applicazione perché un thread infinito, senza risultati e senza effetti collaterali, come un thread della UI, deve terminare e non terminerà mai.

In conclusione

Con questo articolo abbiamo aggiunto altre due frecce alla nostra faretra dei thread. Adesso sappiamo come passare un parametro ad un thread e conosciamo la differenza tra background e foreground Thread. Trovate il codice dell’esempio nel mio repository GitHub.

Se l’articolo vi è piaciuto non esitate a contattarmi sui miei profili social (trovate i link qui accanto) oppure, se preferite, offritemi un caffè (chi mi conosce sa che sono un caffeinomane).

Pierpaolo Paris

Sono uno dei tanti che sono entrati nel girone infernale di Ingegneria Informatica, e che poi ne è anche uscito. Attualmente lavoro come sviluppatore presso una società di consulenza. Ogni tanto mi diletto con la fotografia.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *