MultiThread

.::Home::.

.::Introduzione::.

1.Panoramica

4.DataBase

.::Presentazioni PPT::.

.::Link::.

.::DownLoad::.

 

Si dice MultiThreaded un programma in grado di eseguire più processi contemporaneamente. Tutti i sistemi operativi Microsoft supportano questa tecnologia a partire da Windows 95 e Windows NT.

Essendo la gestione dei processi (thread) una caratteristica fornita dal CLR, è molto semplice averne accesso, come sarà dimostrato in questa parte del tutorial.

Namespace

In .NET le funzionalità legate ai thread sono definite nel namespace System.Threading.

Per poter esplicitamente riferirsi ad esse è necessario utilizzare:

using System.Threading; 
              

Iniziare un Thread

La classe Thread del namespace System.threading rappresenta l'oggetto Thread. Usando questo oggetto è possibile creare nuovi thread, cancellarli, metterli in pausa e farli ripartire.

La classe Thread crea un nuovo thread e il metodo Thread.Start lo avvia:

Thread Mythread = new Thread(new ThreadStart(MyThreadMethod)); 
Mythread.Start();

dove MyThreadMethod è il metodo che verrà eseguito dal thread.

Esempio

Vediamo ora un esempio pratico: il codice che segue crea due thread che scrivono sullo schermo mentre un terzo, principale, fa la stessa cosa:

using System;
using System.Threading; 

class App {
  //Codice del metodo eseguito nel primo Thread 
  public static void MyThreadMethod1() 
  {   
    for ( int i = 0; i<=30; i++ ) 
     { 
      Console.WriteLine("thread 1"); 

     } 
  }
  //Codice del metodo eseguito nel secondo Thread
  public static void MyThreadMethod2() 
  {   
    for ( int j = 0; j<=30; j++ ) 
     { 
      Console.WriteLine("thread 2"); 

     } 
  }

static void Main() {
      //Crea il primo thread
      Thread thread1 = new Thread(new ThreadStart(MyThreadMethod1)); 
      //Avvia il primo thread
      thread1.Start(); 
      //Crea il secondo thread
      Thread thread2 = new Thread(new ThreadStart(MyThreadMethod2)); 
        //Avvia il secondo thread
      thread2.Start(); 
      //Durante l'esecuzione dei 2 thread esegue un terzo ciclo
      for ( int i = 0; i<=30; i++ ) 
        { 
         Console.WriteLine("Main Thread"); 

         } 
      }
}

 

 

Terminare un thread

Il metodo Abort della classe Thread è chiamato per terminare un thread permanentemente. Il metodo IsAlive deve essere chiamata prima di Abort per verificare che il thread sia ancora attivo:

if ( thread.IsAlive ) 
{ 
thread.Abort(); 
}

Mettere in pausa un thread

Un thread può essere messo in pausa temporaneamente dalla sua esecuzione per un periodo di tempo fissato con il metodo thread.Sleep, ad esempio:

thread.Sleep(500);

Impostare la priorità di un thread

La proprietà ThreadPriority è utilizzata per impostare la priorità di esecuzione di un thread. I valori di priorità sono in ordine cresecente: Lowest, BelowNormal, Normal, AboveNormal, Highest.

Ad esempio

thread.Priority = ThreadPriority.Highest;

Imposta la priorità di "thread" alla massima disponibile.

Sospendere un thread

Il metodo Suspend della classe Thread sospende l'esecuzione del thread finchè non è richiamato il metodo Resume.

Nell'esempio che segue il thread "thread" viene sospeso dopo aver verificato che sia attualmente in esecuzione:

if (thread.ThreadState = ThreadState.Running ) 
{ 
thread.Suspend(); 
}

Ripristinare un thread sospeso

Il metodo Resume ripristina un thread sospeso in precedenza. Se il metodo è applicato ad un thread che non è in stato di sospensione, Resume non ha alcun effetto.

Nell'esempio che segue il thread "thread" viene ripristinato dopo aver verificato che sia attualmente in sospeso:

if (thread.ThreadState = ThreadState.Suspended ) 
{ 
thread.Resume(); 
}

 

Sincronizzazione dei thread

Concettualmente i thread sono molto semplici da capire, tuttavia nella pratica, visto che possono interagire sulle stesse strutture dati, possono essere fonte di diversi problemi.

Ad esempio nel primo esempio riportato in questa sezione, l'ordine dell'output su console dei tre processi può cambiare ad ogni esecuzione essendo di fatto stabilito di volta in volta dallo schedulatore dei thread del sistema operativo.

A volte può essere necessario assicurarsi che solo un thread possa accedere ad una determinata struttura dati, o che l'accesso di più thread avvenga in un ordine prefissato perché la struttura stessa si mantenga coerente.

Per garantire che il programma rispetti un comportamento prestabilito nell'accesso alle risorse è necessario coordinare le azione dei thread mediante l'uso di "lock".

E' possibile acquisire il lock per un oggetto al quale si fa riferimento in modo da bloccare gli accessi da parte degli altri thread, solo quello che ha il lock può accedervi.

L'esempio più semplice di accesso concorrente ad una struttura dati è quello di una variabile condivisa che deve essere aggiornata da due thread different. Per fare questo si può utilizzare la classe System.Threading.Interlocked.

Ad esempio per incrementare o decrementare una variabile condivisa chiamata num è possibile usare Interlocked.Increment(num) o Interlocked.Decrement(num), oppure le altre funzioni per impostare un valore specifico.

Le operazioni legate ad Interlocked sono da considerarsi atomiche.

Se esiste una sezione di codice nel metodo di un oggetto che non dovrebbe essere acceduto contemporaneamente da più thread allora è possibile utilizzare la classe Monitor .

La classe monitor consiste di funzioni statiche per entrare ed uscire da un blocco di codice protetto. Non è possibile istanziare un'istanza del tipo Monitor. Invece si passa alle sue funzioni statiche un riferimento ad un tipo qualunque derivato da un oggetto che si vuole utilizzare come singolo nodo di sincronizzazione. Per accedere alle funzioni statiche del Monitor in C# si utilizza appunto la keyword lock.

Per un maggior controllo sulla sincronizzazione sui thread o per la sincronizzazione intra-processo si utilizza la classe Mutex, un oggetto di sincronizzazione che può essere ottenuto da ogni thread in ogni processo. Ogni volta che si crea o si ottiene un mutex, si utilizza il suo metodo GetHandle per ottenere un handle da utilizzare con i metodi WaitHandle.WaitAny o WaitHandle.WaitAll. Questi due metodi sono bloccanti e ritornano solo se lo specifico handle è segnalato(ovvero il mutex non viene usato da un alto thread) o se avviene uno specifico timeout. Dopo aver ottenuto il mutex si eseguono le necessarie operazioni di sincronizzazione e poi si chiama il metodo Mutex.ReleaseMutex per rilasciarlo.

A volte può servire un meccanismo per fare in modo che un thread notifichi agli altri informazioni su di un evento. In questo caso si utilizzano le classi per la sincronizzazione degli eventi ManualResetEvent e AutoResetEvent.

Nell'ambito della sincronizzazione dei thread, un evento è un è un oggetto che può assumere 2 stati: signaled e nonsignaled. Un evento dispone di un handle che può essere utilizzato con WaitHandle come un mutex. Un thread che attende un evento sarà bloccato finchè un altro thread segnala l'evento chiamando ManualResetEvent.Set o AutoResetEvent.Set. Se si usa ManualResetEvent è necessario usare il suo metodo Reset per riportarlo allo stato nonsignaled. Un AutoResetEvent riporta automaticamente allo stato nonsignaled appena al thread in attesa è notificato che l'evento è diventato signaled.

Molte applicazioni basate sui thread creano processi che passano la maggior parte del loro tempo in uno stato di attesa per una certa condizione (un tasto premuto, una operazione di I/O, ecc.). C# fornisce una oggetto System.Threading.ThreadPool per facilitare la risoluzione del problema.

Il miglior modo di utlizziare l'oggettoThreadPool consiste nell'aggiungere un nuovo thread con un evento scatenante (del tipo "Quando accade l'evento fai questo.") al pool.

Usando ThreadPool e un paradigma di programmazione basato sugli eventi il programma può registrare un oggetto System.Threading.WaitHandle (essendo i WaitHandle i modelli a oggetti di C# del paradigma di "wait and notify") e un delegate System.Threading.WaitOrTimerCallback. Piuttosto che avere un thread completo in attesa che WaitHandle finisca, il ThreadPool controlla tutti i WaitHandle registrati con esso per poi chiamare l'appropriato delegate WaitOrTimerCallback quando il WaitHandle viene rilasciato (creando di fatto un metodo di callback per la classe ThreadPool).

In più .NET Framework fornisce un pratico metodo per accodare i lavori ed assegnarli ad un thread del pool. Questa procedure è usata comunemente nelle applicazioni server che gestiscono diversi lavori e richieste concorrenti. Ad esempio un'applicazione che attende per un file di input e li inserisce in un database potrebbe accodare ogni file per l'elaborazione in un thread separato del pool.

La classe System.Threading.ThreadPool permette di accodare lavori usando il metodo QueueUserWorkItem.

L'esempio seguente dimostra come utilizzare la classe Monitor per la sincronizzazione tra thread; vengono accodate cinque chiamate asincrone ad un metodo. Ogni metodo ottiene il lock per "this", scrive sulla console, va in pausa e poi scrive sulla console un'altra volta. La funzione statica Monitor è utilizzata per imporre l'accesso mutualmente esclusivo ad un blocco di codice protetto:

using System;
using System.Threading;


class Resource {
   public void Access(Int32 threadNum) {
      lock (this) {
         Console.WriteLine("Start Resource access (Thread={0})", threadNum);
         Thread.Sleep(500);
         Console.WriteLine("Stop  Resource access (Thread={0})", threadNum);
      }
   }
}


class App {
   static Int32 numAsyncOps = 5;
   static AutoResetEvent asyncOpsAreDone = new AutoResetEvent(false);
   static Resource res = new Resource();

   public static void Main() {
      for (Int32 threadNum = 0; threadNum < 5; threadNum++) {
         ThreadPool.QueueUserWorkItem(new WaitCallback(UpdateResource), threadNum);
      }

      asyncOpsAreDone.WaitOne();
      Console.WriteLine("All operations have completed.");
   }

   // La signature del metodo di callback deve essere uguale a quella di  
   // di un delegate System.Threading.TimerCallback
static void UpdateResource(Object state) {
      res.Access((Int32) state);
      if (Interlocked.Decrement(ref numAsyncOps) == 0)
         asyncOpsAreDone.Set();
   }
}

 

Fonti:

Documentazione .NET Framework SDK

http://www.c-sharpcorner.com/2/mt_beginner1.asp

http://www.oreillynet.com/pub/a/dotnet/2001/08/06/csharp.html

http://www.csharphelp.com/archives/archive128.html

http://www.codeproject.net/dotnet/multithread.asp

http://www.crackinguniversity2000.it/dotnet/Visual%20Studio_NET%20_NET%20Framework.htm

.::^top^::.

(2002) A cura di Carlo Becchi