28
Scrivere Applicazioni Multi Threading in .NET Di Massimiliano Brolli

Multithread Programming

Embed Size (px)

Citation preview

Page 1: Multithread Programming

Scrivere Applicazioni Multi Threading in .NET Di Massimiliano Brolli

Page 2: Multithread Programming

Premessa ................................................................................................... 3

Prerequisiti ................................................................................................ 3

Il Multi Threading ....................................................................................... 4

Le basi ...................................................................................................... 5

Il Namespace .......................................................................................... 5

Le Dichiarazioni ....................................................................................... 5

Lanciare un thread ................................................................................... 5

Meccanismi di Sincronizzazione .................................................................... 6

Sleep ...................................................................................................... 6

Start ...................................................................................................... 7

ResetAbort .............................................................................................. 7

Suspend ................................................................................................. 7

Resume .................................................................................................. 7

Join ........................................................................................................ 8

Interrupt ................................................................................................. 8

Abort ...................................................................................................... 9

SpinWait ............................................................................................... 10

Stato dei thread ....................................................................................... 11

La priorità dei thread .............................................................................. 13

Scopo delle variabili ............................................................................... 15

Pool di Thread .......................................................................................... 18

Gli eventi ................................................................................................. 22

I Timer .................................................................................................... 24

Apartment Thread e interoperabilità COM .................................................... 25

Conclusioni .............................................................................................. 27

Bibliografia .............................................................................................. 28

Page 3: Multithread Programming

Premessa Molto spesso ci siamo imbattuti nel termine AJAX, acronimo di Asynchronous JavaScript and XML ovvero un sistema che permette alle pagine web di agire in maniera asincrona. Lo scopo di questo documento e far capire tramite semplici esempi le componenti/funzionalità offerte da questo straordinario Framework messo a disposizione anche in ambiente .NET.

Prerequisiti Per procedere alla lettura di questo documento occorre aver letto le seguenti dispense :

• Introduzione agli algoritmi. • La scelta tra Microsoft.NET & Java • Predisposizione Virtual Machine di base • Predisposizione Virtual Machine per l'ambiente di sviluppo Microsoft .NET • Introduzione alla scrittura del codice .NET • Scrivere applicazioni .NET con Visual Studio • Panoramica sulla programmazione ad oggetti • La Storia • Introduzione al Framework .NET • Gli oggetti ADO.NET • Predisposizione Ambiente SQL Server • Applicazioni Windows Forms • ADO.NET Entity Framework • Scrivere ASP.NET Web Application, premessa e predisposizione • Scrivere ASP.NET Web Application, La prima applicazione Web Forms • Scrivere ASP.NET Web Application, I Web Services • Scrivere ASP.NET Web Application, Applicazioni MVC • Scrivere ASP.NET Web Application, AJAX

Page 4: Multithread Programming

Il Multi Threading

Il MultiThreading comparso con Windows NT 3.1 e successivamente con Windows 95 rappresenta una rivoluzione nel campo informatico di largo consumo. Solamente i linguaggi di basso livello prima dell’evento del Framework .NET potevano realizzare applicazioni MultiThreading come ad esempio MSVC++ con l’ausilio di particolari librerie. In questo documento andremo ad analizzare le istruzioni che occorre imparare per lavorare con i Thread utilizzando Microsoft.NET Framework 2.0 ed il linguaggio VB.NET. Un Thread o filo in inglese, rappresenta un processo che può lavorare parallelamente al padre chiamante. Nel framework.NET si può far lavorare in un Thread separato porzioni di codice quali Sub Routine o intere classi per realizzare applicazioni performanti che hanno come limite solamente la fantasia. Addentrandoci nel documento cominceremo a familiarizzare con i meccanismi di programmazione Asincrona che introdurranno problematiche nuove quali la concorrenza applicativa e i meccanismi di sincronizzazione.

Page 5: Multithread Programming

Le basi

Il Namespace

Per realizzare applicazioni MultiThreading occorre utilizzare il nameSpace System.Threading come viene mostrato nell’esempio successivo. Imports System.Threading

Le Dichiarazioni

Per lavorare con i Thread occorre dichiarare un oggetto di tipo ThreadStart che permette di eseguire lo Start di un Thread e un oggetto Thread che ci permetterà di crearlo. Private _ThreadStart As ThreadStart

Private _Thread As Thread

Lanciare un thread

Come detto in precedenza è possibile far lavorare su un thread separato porzioni di codice appartenenti ad una Sub Routine oppure una intera classe. Nell’esempio successivo viene lanciato in un Thread separato una Sub Routine ClientGoSend. L’istruzione _ClientThread.Start permetterà di lanciare il Thread come viene mostrato nell’esempio successivo. _ThreadStart = New ThreadStart(AddressOf ClientGoSend)

_Thread = New Thread(_ThreadStart)

_Thread.Start

Public sub ClientGoSend

End sub

Page 6: Multithread Programming

Meccanismi di Sincronizzazione

I Thread essendo delle entità separate rispetto al processo chiamante che li ha creati hanno una loro vita, questo vuol dire che mentre il padre effettuerà ad esempio un loop infinito il Thread creato in precedenza prosegue in parallelo la propria attività. Questo meccanismo introduce alla programmazione Asincrona che seppur utile aggiunge delle problematiche di sincronismo alle nostre applicazioni. Il .NET Framework ci mette a disposizione dei metodi nella classe thread che permettono di attendere per un determinato tempo un qualche evento oppure ripartire nell’elaborazione allo scatenare di un altro.

Sleep

L’istruzione Thread.Sleep(100) mette in attesa il thread per un valore in Millisecondi. _ThreadStart = New ThreadStart(AddressOf Send)

_Thread = New Thread(_ThreadStart)

_Thread.Start

Console.WriteLine(“Main” & AppDomain.GetCurrentThreadId())‘del thread Main

Public sub Send

Dim objThread As Thread = Thread.CurrentThread()

objThread.Sleep(100) ‘Mette in attesa il thread per 100 Millisecondi

Console.WriteLine(“Thread” & AppDomain.GetCurrentThreadId()) ‘ID del thread

objThread.Abort ‘Distrugge il Thread

End sub

L’istruzione Console.WriteLine(AppDomain.GetCurrentThreadId()) viene ripetuta per due volte all’interno dell’applicazione perché stamperà a console prima l’ID del processo chiamante e poi l’ID del processo chiamato. Il risultato sarà Main 2574

Thread 2575

Oppure

Thread 2575

Main 2574

Questo perchè potrà eseguire l’istruzione Console prima il Main oppure prima il Thread creato. Di seguito vengono riportati dei metodi della classe thread che servono per la sincronizzazione dei Thread.

Page 7: Multithread Programming

Start

L’istruzione Start lancia un thread.

ResetAbort

L’istruzione ResetAbort interrompe la distruzione di un thread.

Suspend

L’istruzione Suspend mette in attesa il thread.

Resume

L’istruzione Resume riattiva il thread.

Page 8: Multithread Programming

Join

L’istruzione Join mette in attesa il thread. Nell’esempio sotto riportato, il codice Main chiama un thread che terminirà allo scadere di 4 secondi e scriverà “Worker End”. Imports System.Threading

Module Esempio01

Private _ThreadStart As ThreadStart

Private _Thread As Thread

Public Sub Worker()

Thread.Sleep(4000)

Console.WriteLine("Worker End")

End Sub

Sub Main()

_ThreadStart = New ThreadStart(AddressOf Worker)

_Thread = New Thread(_ThreadStart)

_Thread.Start()

_Thread.Join() 'Attende che il thread sia completato

Console.WriteLine("Main End")

End Sub

End Module

Eseguendo il codice vedremo che il risultato sarà il seguente Worker End

Main End

Questo perché il main attenderà il completamento del thread _Thread prima di effettuare la scrittura di “Main End” grazie al metodo Join. Se asterischiamo l’istruzione _Thread.Join() e rieseguiamo l’esempio noteremo che il risultato sarà il seguente. Main End

Worker End

Questo perché il Main non attenderà la conclusione del Thread _Thread che scriverà dopo 4 secondo il messaggio “Worker End”

Il metodo Join può prendere in input un parametro espresso in millisecondi/Timestamp. Tali millisecondi/Timestamp stanno ad indicare il tempo massimo che verrà atteso per la terminazione del Thread.

Interrupt L’istruzione Interrupt interrompe lo stato di attesa di un thread.

Page 9: Multithread Programming

Abort L’istruzione Abort distrugge il thread. E’ da notare che tale istruzione non è una garanzia di distruzione in quanto genera al suo interno un’eccezione di tipo ThreadAbortException e imposta lo stato su AbortRequested. Se all’interno di questa eccezione venisse invocato il metodo ResetAbort verrebbe vanificata la chiamata di Abort precedentemente invocata e quindi è importante fare attenzione al codice che stiamo implementando tenendo conto di questi meccanismi di concorrenza. La stessa cosa vale per il metodo Interrupt che a sua volta genera un’eccezione di tipo ThreadInterruptedException e imposta lo stato su Running anche se interromperà l’esecuzione. Il codice seguente mostra il comportamento di quanto descritto sopra.

Imports System.Threading

Module Esempio02

Private _ThreadStart As ThreadStart

Private _Thread As Thread

Sub Main()

_ThreadStart = New ThreadStart(AddressOf Worker)

_Thread = New Thread(_ThreadStart)

_Thread.Start()

_Thread.Abort() 'Da provare cambiandolo in Interrupt

Console.WriteLine("Main End")

End Sub

Public Sub Worker()

Dim objThread As Thread = Thread.CurrentThread()

Try

Console.WriteLine("Worker Sleeping")

Thread.Sleep(4000)

'generata se invocato il metodo interrupt

Catch e As ThreadInterruptedException

Console.WriteLine("Worker interrupt : ThreadState " & _

Thread.CurrentThread.ThreadState.ToString)

'generata se invocato il metodo Abort

Catch e As ThreadAbortException

Console.WriteLine("Worker Abort : ThreadState " & _

Thread.CurrentThread.ThreadState.ToString)

'operazioni conclusive del costrutto Try/Catch

Finally

Console.WriteLine("Worker End : ThreadState " & _

Thread.CurrentThread.ThreadState.ToString)

End Try

Console.WriteLine("Worker Completed")

End Sub

Page 10: Multithread Programming

End Module

Eseguendolo il risultato sarà il seguente Worker Sleeping

Worker Abort : ThreadState AbortRequested

Worker End : ThreadState AbortRequested

Main End

Da notare che il thread non è stato completato ( non viene notificato “Worker Completed”) in quanto è stata richiesta una operazione di Abort che viene anche notificata dall’istruzione Thread.CurrentThread.ThreadState.ToString che ci mostra lo stato del thread impostato su AbortRequested. Se al posto di _ClientThread.Abort() inserissimo _ClientThread.Interrupt() nel main del modulo il risultato cambierà in questo modo. Worker Sleeping

Main End

Worker interrupt : ThreadState Running

Worker End : ThreadState Running

Worker Completed

Da notare che il motodo Interrupt ha interrotto l’esecuzione del thread senza distruggerlo, infatti in questo esempio viene notificata la terminazione del thread dal messaggio “Worker Completed” che ci avverte che l’invocazione di interrupt ha interrotto lo stato di Sleep del thread.

SpinWait

L’istruzione SpinWait viene utilizzata su applicazione che girano su macchine multiprocessore e costituisce un’alternativa al comando Sleep che restituisce cicli di CPU rimanendo in attesa.

Page 11: Multithread Programming

Stato dei thread I Thread possono assumere diversi stati in funzione della loro sincronizzazione con i processi chiamanti. In questa sezione andremo ad analizzare i diversi stati per capirne meglio il loro utilizzo. Come visto in precedenza, per acquisire lo stato di un thread occorre accedere al metodo Shared Thread.CurrentThread.ThreadState.ToString che ci ritorna un valore in formato Stringa dello stato corrente del thread. Running

La parola stessa ci dice che il thread sta in esecuzione

Imports System.Threading

Module Esempio03

Sub Main()

Dim _ThreadStart As New ThreadStart(AddressOf Worker)

Dim _Thread As New Thread(_ThreadStart)

_Thread.Start()

While True

Thread.Sleep(1000)

Console.WriteLine("Worker End : ThreadState " & _

_Thread.ThreadState.ToString)

End While

Console.WriteLine("Main End")

End Sub

Public Sub Worker()

While True

End While

Console.WriteLine("Worker Completed")

End Sub

End Module

WaitSleepJoin

Questo stato ci comunica che il thread si trova in uno stato di

Wait, Sleep o Join.

Imports System.Threading

Module Esempio04

Sub Main()

Dim _ThreadStart As New ThreadStart(AddressOf Worker)

Dim _Thread As New Thread(_ThreadStart)

_Thread.Start()

While True

Thread.Sleep(1000)

Console.WriteLine("Worker End : ThreadState " & _

_Thread.ThreadState.ToString)

End While

Console.WriteLine("Main End")

Page 12: Multithread Programming

End Sub

Public Sub Worker()

While True

Thread.Sleep(1000)

End While

Console.WriteLine("Worker Completed")

End Sub

End Module

In questo caso è lo stesso Thread che sta effettuando un ciclo While

true ogni un secondo.

AbortRequested

La parola stessa ci dice che per il thread è stata invocata una

Abort.

Per il metodo Abort vi rimando all’esempio del metodo Abort nella

sezione Meccanismi di Sincronizzazione

Page 13: Multithread Programming

La priorità dei thread

In un ambiente Multi Threading è possibile impostare una priorità di esecuzione per ogni singolo thread. Tale meccanismo permette di condividere nel migliore dei modi i cicli di CPU della macchina permettendo ai processi critici di avere una maggiore priorità in termine di esecuzione sui processi secondari o di minor importanza applicativa. I processi Microsoft .NET sono di due tipi, processi di Foreground e processi di Background. Ai processi di Foreground possiamo attribuire una priorità di esecuzione in base ad una Enumerazione contenuta nella classe System.threading.ThreadPriority qua sotto ricreata per maggiore chiarezza.

‘Enumeration che riporta le priorità per i thread di Foreground

Public Enum Priority

Lowest = 0

BelowNormal = 1

Normal = 2

AboveNormal = 3

Highest = 4

End Enum

Nell’esempio successivo abbiamo utilizzato il metodo Priority per assegnare una priorità di esecuzione. E’ importante dire che un Thread creato dal Runtime senza specifica di priorità viene creato come Foreground di tipo Normal.

Imports System.Threading

Module Esempio05

Sub Main()

Dim _ThreadStart As New ThreadStart(AddressOf Worker)

Dim _Thread As New Thread(_ThreadStart)

_Thread.Start()

_Thread.Priority = ThreadPriority.Lowest

Console.WriteLine("Main End")

End Sub

Public Sub Worker()

Thread.Sleep(4000)

Console.WriteLine("Worker Completed, Priority is : " & _

Thread.CurrentThread.Priority.ToString)

End Sub

End Module

Il risultato di questo esempio ritornerà il seguente Output.

Page 14: Multithread Programming

Worker Completed, Priority is : Lowest

Main End

Cambiando il tipo di Priorità da Lowest a Highest noteremo che l’applicazione attenderà sempre la conclusione del thread prima di essere terminata (Ovviamente omettendo il metodo Join). Questo perché si tratta di un thread di tipo Foreground e quindi deve essere per forza terminato prima di terminare l’intero processo. E’ possibile definire un Thread come processo di Background utilizzando la proprietà Thread.IsBackground di tipo Boleano. Impostando a True questo valore il processo potrà essere terminato in qualsiasi momento, talei processi di background saranno processi non di vitale importanza per le nostre applicazioni.

Imports System.Threading

Module Esempio06

Sub Main()

Dim _ThreadStart As New ThreadStart(AddressOf Worker)

Dim _Thread As New Thread(_ThreadStart)

_Thread.Start()

_Thread.IsBackground = True

Console.WriteLine("Main End")

End Sub

Public Sub Worker()

Thread.Sleep(4000)

Console.WriteLine("Worker Completed, Priority is : " & _

Thread.CurrentThread.Priority.ToString)

End Sub

End Module

Il risultato di questo esempio ritornerà il seguente Output.

Main End

Questo perchè il processo figlio è stato definito di Background e quindi è stato terminato senza preavviso dal processo chiamante.

Page 15: Multithread Programming

Scopo delle variabili

E’ possibile creare delle variabili che avranno uno scopo limitato al processo che le sta eseguendo. Nell’esempio che viene riportato in seguito, la variabile di classe Value viene dichiarata inserendo il suffisso <ThreadStatic()>. Questa variabile non sarà più una variabile condivisa sia dal processo chiamante che dal processo figlio, ma ogni processo avrà una copia di tale variabile e ne potrà fare un uso esclusivo.

Imports System.Threading

Module Esempio07

<ThreadStatic()> Public Value As String = "Main"

Sub Main()

Console.WriteLine("Main Value = " & Value)

Dim _ThreadStart As New ThreadStart(AddressOf Worker)

Dim _Thread As New Thread(_ThreadStart)

_Thread.Start()

_Thread.Join()

Console.WriteLine("Main Value = " & Value)

End Sub

Public Sub Worker()

Console.WriteLine("Worker Value = " & Value)

Value = "Worker"

Console.WriteLine("Worker Value = " & Value)

End Sub

End Module

Il risultato di questo esempio ritornerà il seguente Output.

Main Value = Main

Worker Value =

Worker Value = Worker

Main Value = Main

Main End

Vediamo come la variabile Value impostata a “Main” sarà visibile solamente nel Main del programma, mentre il Thread Worker ne potrà fare un uso esclusivo impostando un valore che non verrà condiviso con il programma Main chiamante. E’ possibile utilizzando I Thread definire dei punti di codice nel quale non possono accedervi più Thread contemporaneamente. Questo è possibile grazie all’istruzione SyncLock. Nell’esempio successivo verrà definita una variabile di classe di nome Lock e nel metodo Worker verrà utilizzata l’istruzione SyncLock Lock che tramite l’oggetto globale Lock determinerà chi far accedere alla porzione di codice specifica.

Page 16: Multithread Programming

L’oggetto Lock deve essere definito di tipo Object.

Imports System.Threading

Module Esempio15

Public Counter As Integer

Public Lock As New Object

Sub Main()

Dim i As Integer

For i = 0 To 10

Dim _ThreadStart As New ThreadStart(AddressOf Worker)

Dim _Thread As New Thread(_ThreadStart)

_Thread.Start()

Next

End Sub

Public Sub Worker()

‘SyncLock Lock

Counter = Counter + 1

Thread.Sleep(New Random().Next(500, 2000))

Console.WriteLine("Worker Counter : " & Counter)

‘End SyncLock

End Sub

End Module

Il risultato di questo esempio ritornerà il seguente Output. Questo perché la display del contatore avverrà dopo che tutti i thread avranno effettuato la somma.

Worker Counter : 11

Worker Counter : 11

Worker Counter : 11

Worker Counter : 11

Worker Counter : 11

Worker Counter : 11

Worker Counter : 11

Worker Counter : 11

Worker Counter : 11

Worker Counter : 11

Worker Counter : 11

Se disasterischiamo le istruzioni SyncLock e rieseguiamo l’esempio vedremo che il risultato cambierà in questo modo.

Worker Counter : 1

Worker Counter : 2

Worker Counter : 3

Worker Counter : 4

Worker Counter : 5

Worker Counter : 6

Worker Counter : 7

Worker Counter : 8

Worker Counter : 9

Worker Counter : 10

Worker Counter : 11

Page 17: Multithread Programming

Questo perché il blocco di codice ha un lock determinato dall’oggetto Lock definito come variabile di classe che ne restituisce il sincronismo. Da notare che il primo thread accederà al blocco e gli altri rimarranno in attesa fino a quando il primo thread non accederà all’istruzione End SyncLock, a quel punto il secondo entrerà in azione e così via.

Page 18: Multithread Programming

Pool di Thread I Pool di Thread vengono in nostro aiuto quando occorre creare dei processi di lunga durata che lavorano in parallelo. Un classico esempio sono quelle applicazioni Server (Web Server, SMTP Server ecc..) che non possono smaltire una richiesta alla volta ma debbono prendere in carico le richieste e processarle in parallelo. Quindi l’utilizzo di Pool di Thread risulta complesso e oneroso in termine di carico di CPU e quindi se non espressamente richiesto ne faremo volentieri a meno. A nostro aiuto viene la classe ThreadPool che ci consente di creare Thread tramite l’ausilio del metodo QueueUserWorkItem. Tutti i Thread creati da un ThreadPool vengono avviati con Priority di Background.

Imports System.Threading

Module Esempio08

Sub Main()

Dim i As Integer

For i = 0 To 10

ThreadPool.QueueUserWorkItem( _

New WaitCallback(AddressOf Worker), i)

Next

Console.WriteLine("Main id : " & _

AppDomain.GetCurrentThreadId())

Thread.Sleep(5000)

End Sub

Public Sub Worker(ByVal IndexThread As Object)

Console.WriteLine("Thread N° " & IndexThread & " id : " & _

AppDomain.GetCurrentThreadId())

End Sub

End Module

In questo esempio vengono creati tramite la classe ThreadPool dei thread di tipo Worker che verranno eseguiti parallelamente. Il risultato prodotto sarà il seguente.

Main id : 2008

Thread N° 0 id : 532

Thread N° 1 id : 532

Thread N° 2 id : 532

Thread N° 3 id : 532

Thread N° 4 id : 532

Thread N° 5 id : 532

Thread N° 6 id : 532

Thread N° 7 id : 532

Thread N° 8 id : 532

Thread N° 9 id : 532

Thread N° 10 id : 532

Page 19: Multithread Programming

La cosa che ci viene subito all’occhio è che l’ID dei thread lanciati sono sempre gli stessi. Questo perché appena verrà accodato (i = 0) al ThreadPool una nuova richiesta, il ThreadPool creerà un nuovo Thread e lo eseguirà. Se in un tempo relativamente breve il Thread verrà terminato, il ThreadPool riutilizzerà lo stesso Thread per l’elaborazione della coda successiva (i=1 , i=2, i=3. i=4 ecc..) Quindi l’ID dei Thread processati sarà sempre lo stesso in quanto verrà riutilizzata la stessa allocazione di memoria e quindi non verrà effettuato un vero parallelismo di applicazione. Ovviamente assumere che il termine dei Thread avvenga in modo lineare è estremamente pericoloso (N°1, N°2, N°3, N°4, N°5, N°6, N°7, N°8, N°9, N°10) Il codice successivo invece mostra un vero parallelismo applicativo in quanto il termine dei Thread creati dal ThreadPool avrà un tempo di durata maggiore del tempo minimo di esecuzione di un Thread. Questo permetterà al ThreadPool di creare Thread con ID differenti e farli lavorare in parallelo. L’istruzione Thread.Sleep(New Random().Next(2000)) ci permette di simulare una durata casuale della funzione Worker.

Imports System.Threading

Module Esempio09

Sub Main()

Dim i As Integer

For i = 0 To 10

ThreadPool.QueueUserWorkItem( _

New WaitCallback(AddressOf Worker), i)

Next

Console.WriteLine("Main id : " & AppDomain.GetCurrentThreadId())

Thread.Sleep(5000)

End Sub

Public Sub Worker(ByVal IndexThread As Object)

Thread.Sleep(New Random().Next(2000))

Console.WriteLine("Thread N° " & IndexThread & " id : " & _

AppDomain.GetCurrentThreadId())

End Sub

End Module

Il risultato prodotto sarà il seguente.

Thread N° 1 id : 3356

Thread N° 0 id : 3644

Thread N° 4 id : 3644

Thread N° 7 id : 1256

Thread N° 2 id : 540

Thread N° 3 id : 3356

Thread N° 6 id : 3644

Page 20: Multithread Programming

Thread N° 9 id : 540

Thread N° 10 id : 3356

Thread N° 5 id : 2968

Thread N° 8 id : 1256

Ovviamente alcuni Thread potranno essere riutilizzati dal ThreadPool e quindi manterranno ID analoghi. Il ThreadPool ha dei metodi che possono essere invocati per facilitare il suo utilizzo. Di default il numero massimo di esecuzione parallela è impostato a 25 Thread che tramite il metodo SetMaxThreads può essere variato.

Dim MaxThread As Boolean = ThreadPool.SetMaxThreads(100, 1)

restituisce un valore di tipo Boleano se la richiesta è andata a buon fine. Tale metodo prende in input due valori, Workerthreads che definisce il numero massimo di thread presenti nel Pool e completionPortThreads che prende in input un numero corrispondente alle CPU sulle quali si vuole far girare il Pool. Ovviamente una volta terminato il numero di Thread massimi in esecuzione nel Pool, le richieste rimarranno in sospeso fino al liberarsi di un Thread appartenente al Pool. L’esempio successivo invece utilizza il metodo SetMaxThreads e GetAvailableThreads che ritorna il numero dei thread disponibili all’interno del Pool. Tale valore è la sottrazione tra il numero massimo dei Thread impostati tramite SetMaxThreads e il numero dei thread in esecuzione.

Imports System.Threading

Module Esempio10

Sub Main()

Dim i As Integer

Dim MaxThread As Boolean = ThreadPool.SetMaxThreads(10, 1)

Console.WriteLine("SetMaxThreads : " & MaxThread)

For i = 0 To 10

Dim ReturnAddTopool As Boolean = _

ThreadPool.QueueUserWorkItem( _

New WaitCallback(AddressOf Worker), i)

Next

Thread.Sleep(5000)

End Sub

Public Sub Worker(ByVal IndexThread As Object)

Thread.Sleep(New Random().Next(2000))

Dim WorkerThreads As Integer

Dim ThreadId As Integer = AppDomain.GetCurrentThreadId()

Dim CompletionPortThreads As Integer

ThreadPool.GetAvailableThreads(WorkerThreads, _

CompletionPortThreads)

Console.WriteLine("Thread Available is : " & _

WorkerThreads)

Page 21: Multithread Programming

Console.WriteLine("Thread N° " & IndexThread & " id : " & _

ThreadId)

End Sub

End Module

Il risultato sarà il seguente. SetMaxThreads : True

Thread Available is : 9

Thread N° 0 id : 2064

Thread Available is : 9

Thread N° 1 id : 2064

Thread Available is : 7

Thread N° 3 id : 3784

Thread Available is : 6

Thread N° 5 id : 3784

Thread Available is : 6

Thread N° 2 id : 2064

Thread Available is : 5

Thread N° 9 id : 3596

Thread Available is : 5

Thread N° 8 id : 2064

Thread Available is : 6

Thread N° 4 id : 3320

Thread Available is : 7

Thread N° 7 id : 3784

Thread Available is : 8

Thread N° 6 id : 344

Thread Available is : 9

Thread N° 10 id : 3596

Come vediamo Thread Available è variabile in base a diversi fattori, la cosa che salta subito all’occhio e che ci saremmo aspettati dalle print del 2 thread che i Thread disponibili siano minori di 9. Come visto in precedenza il ThreadPool definisce un tempo entro il quale allocare una seconda risorsa per attivare un nuovo Thread. Se la precedente richiesta finisce entro un tempo relativamente breve riutilizza la stessa (e per questo mantiene anche lo stesso ID di processo).

Page 22: Multithread Programming

Gli eventi

Fino ad ora abbiamo creato un Thread passandogli un indirizzo di una Sub Routine dichiarata all’interno della funzione Main(), come detto in precedenza è possibile istanziare una classe, passargli dei parametri e poi eseguire in un thread separato una Routine presente di quella classe. Nell’esempio successivo viene dichiarata una classe che al suo interno dispone di un evento TerminateThread che verrà generato per segnalare al Main() il completamento delle operazioni.

Imports System.Threading

Module Esempio11

Public WithEvents _Worker As Worker

Sub Main()

_Worker = New Worker

_Worker.TimeSleep = 2000

Dim _ThreadStart As New ThreadStart(AddressOf _Worker.Start)

Dim _Thread As New Thread(_ThreadStart)

_Thread.Start()

Console.WriteLine("Main End")

End Sub

Private Sub _Worker_TerminateThread(ByVal Result As String) _

Handles _Worker.TerminateThread

Console.WriteLine(Result)

End Sub

End Module

Public Class Worker

Public Event TerminateThread(ByVal Result As String)

Public TimeSleep As Integer

Public Sub Start()

Thread.Sleep(TimeSleep)

RaiseEvent TerminateThread("Worker Completing. TimeSleep is : " & _

TimeSleep)

End Sub

End Class

I risultato dell’elaborazione è il seguente

Main End

Worker Completing. TimeSleep is : 2000

Questo meccanismo di segnali ci permetterà di avere un controllo preciso su ciò che sta succedendo all’interno del Thread. Ma è possibile anche mettere in attesa un Thread fino a che non si verifichi una condizione per cui far continuare l’elaborazione senza utilizzare i comandi di sincronizzazione Suspend e poi il relativo Resume.

Page 23: Multithread Programming

Nel codice successivo utilizzeremo il comando AutoResetEvent che ci permette di utilizzare i metodi WaitOne() e Set() con cui mettere in attesa un Thread (WaitOne) e poi successivamente dal Main far continuare l’elaborazione (Set). Oltre a WaitOne() è possibile mettere in attesa il Thread tramite WaitAny() e WaitAll(). WaitAny() mette in attesa il Thread fino a quando un evento venga segnalato. WaitAll() invece mette in attesa il Thread fino a che tutti gli eventi vengano segnalati. Ovviamente l’evento viene segnalato quando viene evocato il metodo Set(). Questo genere di meccanismi ci permettono di controllare perfettamente il codice che viene processato all’interno dei Thread.

Imports System.Threading

Module Esempio12

Public _AutoResetEvent As AutoResetEvent

Sub Main()

_AutoResetEvent = New AutoResetEvent(False)

Dim _ThreadStart As New ThreadStart(AddressOf Worker)

Dim _Thread As New Thread(_ThreadStart)

Console.WriteLine("Main : Start Thread")

_Thread.Start()

Console.WriteLine("Main Slepp 1 Second...")

Thread.Sleep(1000)

Console.WriteLine("Main Signal Worker Thread")

_AutoResetEvent.Set()

End Sub

Public Sub Worker()

Console.WriteLine("Worker thread Active")

_AutoResetEvent.WaitOne()

Console.WriteLine("Worker thread Completing")

End Sub

End Module

I risultato dell’elaborazione è il seguente

Main : Start Thread

Worker thread Active

Main Slepp 1 Second...

Main Signal Worker Thread

Worker thread Completing

Page 24: Multithread Programming

I Timer Il nameSpace System.Threading comprende un oggetto molto utile che andremo ad analizzare che si chiama Timer. Il timer viene in nostro aiuto quando occorre far eseguire porzioni di codice a tempo. Nell’esempio successivo viene mostrato l’utilizzo di un timer, da tenere in considerazione che questo tipo di classe può essere utilizzata anche all’interno dei Windows Service quando non è possibile utilizzare la classe Timer presente nelle Windows Form. Il costruttore della classe Timer prende in input un oggetto di tipo TimerCallBack che identifica la locazione di memoria da andare ad eseguire, lo State, un oggetto contenente i valori da passare al metodo da eseguire, dueTime che specifica il tempo che verrà calcolato prima della prima chiamata (0 vuol dire subito) e infine il Period che corrisponde al tempo in millisecondi tra una chiamata e l’altra. Imports System.Threading

Module Esempio13

Sub Main()

Dim _TimerCallback As New TimerCallback(AddressOf Worker)

Dim _Timer As Timer = New Timer(_TimerCallback, _

Nothing, _

0, _

1000)

Thread.Sleep(6000)

End Sub

Public Sub Worker(ByVal State As Object)

Console.WriteLine("Worker Now : " & DateTime.Now.ToLongTimeString())

End Sub

End Module

Il risultato sarà il seguente.

Worker Now : 9.27.43

Worker Now : 9.27.44

Worker Now : 9.27.45

Worker Now : 9.27.46

Worker Now : 9.27.47

Worker Now : 9.27.48

Worker Now : 9.27.49

Page 25: Multithread Programming

Apartment Thread e interoperabilità COM

In ultima analisi vediamo in che maniera far collaborare i Thread .NET con il vecchio mondo COM. Per questo motivo avremo senz’altro notato delle proprietà presenti all’interno dell’oggetto Thread denominate SetApartmentState() e GetApartmentState(). L’Apartment State è un contenitore logico presente in un processo dove sono allocati oggetti che condividono gli stessi requisiti di accesso ad un Thread. Tele specifica esiste nella vecchia COM ma non è presente all’interno del Common Language Runtime di .NET. I Componenti COM grazie all’Apartment garantivano una corretta sincronizzazione delle risorse e per questa loro logica di implementazione che occorre modificare i nostri Thread se vogliamo far uso di componenti COM dal loro interno. La proprietà SetApartmentState() prende in input i valori dati dall’enumerazione riportata in seguito per comodità.

Public Enum ApartmentState

STA = 0 'Single thread Apartment

MTA = 1 'MultiThread Apartment

unknow = 2 'Non definito

End Enum

Se non viene dichiarato un ApartmentState() lo stato prelevato dalla proprietà GetApartmentState() sarà impostato su unknow. l’oggetto COM se noterà un Apartment incompatibile utilizzerà un proxy per farlo lavorare correttamente. Per migliorare le performance di comunicazione tra il thread chiamante e l’oggetto COM è meglio specificare il corretto stato di Apartment da utilizzare. Non è possibile modificare una volta avviato il Thread lo stato di Apartment ne annullarne l’inizzializzazione. Imports System.Threading

Imports ComDemo

Module Esempio14

Sub Main()

Dim _ThreadStart As New ThreadStart(AddressOf Worker)

Dim _Thread As New Thread(_ThreadStart)

Try

Console.WriteLine("Worker ApartmentState : " & _

_Thread.GetApartmentState.ToString)

_Thread.TrySetApartmentState(ApartmentState.STA)

Console.WriteLine("Worker ApartmentState : " & _

_Thread.GetApartmentState.ToString)

Page 26: Multithread Programming

_Thread.Start()

Catch ex As ThreadStateException

Console.WriteLine("ThreadStateException occurs " & ex.Message)

End Try

_Thread.Join()

End Sub

Sub Worker()

Dim ComObject As New ComDemo.Class1

Console.WriteLine("Worker invoke SetValue")

ComObject.SetValue("Inizialize")

Thread.Sleep(2000)

Dim ComValue As String = ComObject.GetValue

Console.WriteLine("Worker invoke GetValue : " & ComValue)

End Sub

End Module

Il risultato sarà il seguente.

Worker ApartmentState : Unknown

Worker ApartmentState : MTA

Worker invoke SetValue

Worker invoke GetValue : Inizialize

Questo esempio fa vedere che è possible impostare l’ApartmentState solamente in fase di creazione del thread prima di aver invocato il metodo Start().

Page 27: Multithread Programming

Conclusioni

Il MultiThreading in Microsoft .NET è possibile ed è di semplice utilizzo come abbiamo appreso in questo documento. Le tecniche di Pool di thread già presenti all’interno del Framework facilitano di gran lunga la realizzazione e la progettazione di applicativi scalabili e performanti che fino ad ora erano onerosi e complessi da realizzare. Al programmatore è rivolta la massima accortezza nello scrivere le applicazioni che con l’avvento del MultiThreading introducono meccanismi complessi di tracciamento dei processi e di Locking.

Page 28: Multithread Programming

Bibliografia