EXT402
Async Programming
Raffaele Rialdi
Visual Developer Security MVP
MVP Profile http://snipurl.com/f0cv
[email protected]
Un po' di termini ...
App
Domain
• Il thread è l'unità di esecuzione
– Il processo è l'unità di isolamento (spazio di indirizzamento)
– Ogni processo ha almeno un thread
• Il thread ha il suo TLS (Thread Local Storage) privato
• Il thread può non avere (default) oppure avere un security token
(impersonation)
– Passibile di "luring attack" o elevazione di privilegio
• Il thread ha una priorità
• Lo scheduler di Windows concede ad ogni thread una certa quantità
di tempo di esecuzione che dipende
–
–
–
–
dalla priorità
dal numero di thread totali
dal tipo di OS (server vs workstation)
dal numero di CPU
Context switch
• Avviene fondalmentalmente in tre casi
1. Quando dal thread viene sospesa l'esecuzione
• chiamando Thread.Sleep oppure
• chiamando WaitOne, WaitAny, WaitAll (che vedremo dopo) oppure
• Win32 ==> (WaitForSingle/MultipleObject/s, MsgWaitForxxx)
2. Quando dal thread viene chiamata
• Win32 SwitchToThread() oppure Thread.Sleep(0) (identico)
3. Quando il quantum a disposizione del thread è terminato.
Non abusare dei thread
• Context switch richiede circa 5000 istruzioni. Gli step
tipici sono:
1. Entrare in Kernel Mode
2. Salvare i registri relativi al thread precedente
3. Acquisire il dispatch spinlock
• oggetto kernel che permette di gestire la concorrenza tra più CPU
4. Determinare il thread successivo da eseguire (se appartiene ad
un altro processo, può essere ancora più costoso)
5. Lasciare il dispatch spinlock.
6. Scambiare in kernel mode lo stato dei thread precedente e
successivo
7. Riprisitnare i registri relativi al nuovo thread
8. Uscire dal Kernel Mode
Azioni non eseguite in caso di Fibers
Perché lavorare in asincrono?
• Asincrono significa che l'esecuzione avviene in un
contesto di esecuzione differente
• Usare più thread invece di più processi
– i thread condividono lo stesso spazio di indirizzamento
– si risparmia molta memoria (svchost.dll)
– i thread fanno meno 'fatica' a comunicare rispetto ai processi
• Motivazioni per lavorere in asincrono
–
–
–
–
–
Per rendere responsiva la UI
Per dare modo all'utente di annullare un'operazione lunga
Per svolgere in modo parallelo dei task
...
Perché la pacchia è finita e i GHz non cresceranno più di tanto
ma i 'core' si
Programmazione asincrona
• Creando esplicitamente un thread
– Thread
– Thread pool
•
–
–
–
•
Oppure lasciando che sia il framework a crearlo per noi
Beginxxx
System.Threading.Timer
...
In ogni caso entrano in gioco i thread
ThreadPool
• Meccanismo di riuso dei thread che minimizza i costi in
performance
• Un solo ThreadPool per processo (non per-appdomain)
• Il pool viene creato con un minimo di 2 thread e non più
di 50 (default modificabili)
• I thread del thread pool lavorano sempre in MTA.
– se si vuole un thread in STA non si può usare il ThreadPool
• Per avviare un thread dal pool:
ThreadPool.QueueUserWorkItem(new WaitCallback(ThreadProc), param);
System.Threading.Thread
• Crea esplicitamente un thread
– Utile per creare un thread nella STA
– Utile per creare un thread che vive molto a lungo
– Non conveniente per creare thread che vivono vita breve (meglio
il ThreadPool)
• Il minimo codice per creare un thread
Thread t = new Thread(new ThreadStart(ThreadProc));
t.Start();
oppure (fx 2.0)
Thread t = new Thread(new ParameterizedThreadStart(ThreadProc));
t.Start(param);
Proprietà più usate di
System.Threading.Thread
• ApartmentState. Determina l'apartment COM nel quale
far girare il thread (Default MTA)
– Impostabile solo prima che il thread parta
(TrySetApartmentState evita l'eccezione)
– Per la main() si usano gli Attributi [STAThread], [MTAThread]
– In Asp.net (default MTA) l'attributo aspcompat=true indica STA
• CurrentCulture. Impostazioni di currency, date, ora, ...
• CurrentUICulture. Impostazioni sulla lingua della UI
(MUI, risorse, etc.)
Proprietà più usate di
System.Threading.Thread
• IsBackground. Se false l'applicazione in chiusura aspetta
che il thread termini
• ManagedThreadId. Identificativo del thread o del fiber
(dipende dall'applicazione host). Rende obsoleta la
AppDomain.GetCurrentThreadId
• Priority. Determina la priorità del thread per lo scheduler
di Windows
• [Flags] ThreadState. Legge lo stato in cui si trova il
thread (avviato, fermato, etc.)
Metodi più usati di
System.Threading.Thread
• Start/Suspend/Resume. Avvia/Sospende/Riavvia
l'esecuzione del thread
• Sleep. Sospende il thread rinunciando al proprio
quantum assegnato dallo scheduler di Windows.
– Il numero di millisecondi indicato è il minimo per cui il thread
non sarà eseguito
• Join. Sospende il thread chiamante fino a che quello
chiamato termina
– Join serve la message pump COM ma non aggiorna la UI
• AllocateDataSlot, GetData e SetData permettono
l'accesso al TLS
LocalDataStoreSlotSlot = Thread.AllocateDataSlot();
– Il CLR elimina e ripristina
il TLS ad ogni cambio di
AppDomain
Thread.SetData(Slot, "Hello");
...
string str = Thread.GetData(Slot) as string;
Comunicazione con i thread
• I Thread di uno stesso processo vivono dentro lo stesso
spazio di indirizzamento, quindi possono condividere la
memoria
– È quindi necessario un meccanismo per regolare l'accesso
esclusivo ad una risorsa per non lasciarla in uno stato
inconsistente.
– Questo implica che l'accesso alla risorsa deve avvenire in tempi
differenti e non contemporaneamente.
• Thread differenti eseguono codice su due differenti linee
temporali.
Spesso è necessario eseguire porzioni di codice di due
(o più) thread in una sequenza
– È quindi necessario un meccanismo per sincronizzarne
l'esecuzione
Ciclo di vita di un thread
• I thread (così come i processi) non vanno killati se non
in casi estremi
– non vengono chiamati i distruttori
– possono lasciare uno stato inconsistente nell'applicazione
Condivisione di memoria
tra thread
!
definizione tipo
(int, struct,class)
new MyType
dati static
(se classe
o struct)
Istanza
Istanza
Istanza
Istanza
!
O
K
Accesso esclusivo
• Critical Sections (Monitor). Oggetto del sistema
operativo che permette di regolare l'accesso esclusivo
ad una risorsa
– Non condivisibile tra processi, è l'oggetto di sincronizzazione più
performante
lock(x)
Object obj = (Object)x; // eventuale boxing
– C#
{ Work(x); }
System.Threading.Monitor.Enter(obj);
try
– VB.net
SyncLock(x)
Work(x)
End SyncLock
{
Work(x);
}
finally
{
System.Threading.Monitor.Exit(obj);
}
Tipologie di locking
• Operazioni garantite atomiche
– Tutte le operazioni di grandezza pari ai registri CPU
– System.Threading.InterLocked
• Add / Decrement (int e long)
• CompareExchange(T v1, T v2, T v3) Se v3==v1 ==> v1=v2
• T res = Exchange(T v1, T v2) v1=v2, res=v1
• System.Threading.ReaderWriterLock
– Lock ottimizzato per molte letture e una sola scrittura
– Ottimale dove la risorsa cambia poco frequentemente
• Il resto deve essere sempre protetto se è possibile che
sia accesso da più thread
Accesso esclusivo - processi differenti
• Mutex è un oggetto kernel analogo per funzionamento
al Monitor
– Si condivide tra processi tramite nome
– Il prefisso "Global\" indica che è globale dell'OS
– Il default è "Local\" cioè relativo alla sola sessione attuale
static void Main()
{
bool bNewInstance;
Mutex Instance = new Mutex(true, @"Global\NomeUnico", out bNewInstance);
if(!bNewInstance)
return;
Form1 f = new Form1();
Application.Run(f);
Instance.ReleaseMutex();
Instance.Close();
}
Sincronizzazione
• AutoResetEvent e ManualResetEvent sono oggetti
kernel che segnalano da un thread differente (anche se
in processi diversi) che si è verificato l'evento
– Classe base: EventWaitHandle
AutoResetEvent ev = new AutoResetEvent(false);
// creazione e start thread secondari
Thread
sospesi
ev.Set();
ev.WaitOne();
ev.WaitOne();
Thread
avviati
• AutoResetEvent ripristina automaticamente a "non
segnalato" l'evento dopo le WaitOne
Sincronizzazione
• WaitHandle ha due metodi statici: WaitAll e WaitAny
AutoResetEvent ev1 = new AutoResetEvent(false);
AutoResetEvent ev2 = new AutoResetEvent(false);
WaitHandle[] evArray = new WaitHandle[] {ev1, ev2};
// creazione e start thread secondari
non
segnalato
segnalato
Thread
sospeso
oppure
ev1.Set();
ev2.Set();
WaitHandle.
WaitAny(evArray);
WaitHandle.
WaitAll(evArray);
Thread
avviato
Sincronizzazione
• WaitOne, WaitAny, WaitAll hanno tutti altri due
overload che prende un timeout per uscire dall'attesa
senza aspettare l'evento (Int32 millisecondi o TimeSpan)
• L'handle al processo è un altro esempio di handle
utilizzabile con questi metodi
– Si può attendere in un thread secondario il termine di un
processo lanciato precedentemente
• Semaphore è un altro oggetto kernel che funge da
arbitro, analogo ai precedenti, che il numero di thread
che possono contemporaneamente accedere ad una
risorsa
• Race Conditions
Timer
• I timer sono tre:
– System.Windows.Forms.Timer (usa la message pump)
– System.Threading.Timer
– System.Timers.Timer (usa internamente
System.Threading.Timer)
• Per impostare scadenze successive è inutile usare più di
unh timer
– si ordina la lista di scadenze e si schedula la differenza tra la
prima e DateTime.Now
• Al posto di usare un timer si può anche usare la
scadenza di WaitHandle con eventi di supporto
– Vedi esempio
Esempio
Timer
Accesso alla message pump di
Windows
• La message pump delle finestre di Windows non può
essere chiamata da thread diversi dal primario
– GDI non rientrante
– in fx 2.0 Control.CheckForIllegalCrossThreadCalls (versione
debug) ci avvisa con un'eccezione
• Il modo canonico è quello di eseguire una Form1.Invoke
invocando un delegate della form
Esempio
FormInvoke
La message pump in Windows
• È sempre necessario
processare la coda dei
messaggi
– in caso negativo
l'applicazione non può
processare WM_PAINT
– "Application is not
responding"
WM_PAINT
WM_MOUSEMOVE
WM_COMMAND
WM_CUT
WM_MOVE
• Il GDI non è rientrante
– eredità del vecchio GDI 16
bit che non si è potuta
cambiare
– varaibili globali impediscono
la rientranza in modo 'stabile'
WM_TIMECHANGE
WM_SETFONT
WM_POWER
WM_HELP
WM_SETICON
BackgroundWorker
Esempio
BGWorker
• Componente che permette di eseguire con semplicità
un'elaborazione lunga in un thread separato
– Si trascina sulla form il componente
– Chiamando RunWorkerAsync si avvia l'elaborazione
– Nell'evento DoWork si esegue l'elaborazione lunga
• Con ReportProgress si informa backgroundworker sulla percentuale
di avanzamento
• Se CancellationPending è true, bisogna impostare e.Cancel=true ed
uscire
– L'evento ProgressChanged può aggiornare la UI direttamente
– L'evento RunWorkerCompleted comunica
• Error = true indica un'eccezione del worker process
• Cancelled = true indica che è stata richiesta l'annullamento
• Altrimenti si aggiorna la UI sul risultato
– Chiamando CancelAsync si chiede l'interruzione
Invocazione asincrona di un metodo
• Il framework prevede un meccanismo semplificato per
eseguire un metodo in un thread del thread pool
– Metodi nella BCL che iniziano per BeginXXX
•
•
•
•
Invocazione dei metodi dei web services
lettura/scrittura su file
nuove librerie di zip/unzip del fx 2.0
.... molte molte altre
– Creazione di un delegate al metodo ed uso del metodo
BeginXXX al pari di quelle presenti nel framework
Invocazione asincrona
Metodo 1: callback
LongRunning lr = new LongRunning(100);
StartDelegate sd = new StartDelegate(lr.Start);
sd.BeginInvoke(5, new AsyncCallback(Callback), sd);
Parametro/i di
Start
metodo che riceve
il risultato
Recupero risultato
Recupero object
della BeginInvoke
object da passare
alla callback
void Callback(IAsyncResult res)
{
StartDelegate sd = res.AsyncState as StartDelegate;
int Result = sd.EndInvoke(res);
}
Invocazione asincrona
Metodo 2: attesa della fine
Parametro/i di
Start
Informazioni sul
risultato async
LongRunning lr = new LongRunning(100);
StartDelegate sd = new StartDelegate(lr.Start);
IAsyncResult res = sd.BeginInvoke(5, null, null);
// altre operazioni nel thread principale ...
int Result = sd.EndInvoke(res);
Attesa e recupero
risultato
Invocazione asincrona
Metodo 3: attesa dell'evento
Parametro/i di
Start
Informazioni sul
risultato async
LongRunning lr = new LongRunning(100);
StartDelegate sd = new StartDelegate(lr.Start);
IAsyncResult res = sd.BeginInvoke(5, null, null);
// altre operazioni nel thread principale ...
res.AsyncWaitHandle.WaitOne(); // attesa fine esecuzione
int Result = sd.EndInvoke(res);
Attesa e recupero
risultato
Invocazione asincrona
Metodo 4: attesa tramite boolean
Informazioni sul
risultato async
Parametro/i di
Start
LongRunning lr = new LongRunning(100);
StartDelegate sd = new StartDelegate(lr.Start);
IAsyncResult res = sd.BeginInvoke(5, null, null);
while(!res.IsCompleted)
{
// altre operazioni nel thread principale ...
// Application.DoEvents() ... solo per winform
Thread.Sleep(10);
}
int Result = sd.EndInvoke(res);
Attesa termine
esecuzione
recupero risultato
Esempio
AsyncDelegate
Caliamo nella realtà
• WinForm, WPF, WCF, WF, .... tutte tecnologie che
hanno e continueranno ad aver bisogno di un thread
switch
• Che fare quindi se i metodi da invocare da thread
differenti sono in numero consistente?
–
–
–
–
dichiarare il delegate
creare un metodo adatto
istanziare il delegate
invocarlo
• ... IDEA!
Esempio pazzo
Uno sguardo al futuro
• Tre fasi in cui Microsoft Research sta lavorando:
– Tools di analisi statica per trovare i bug sui deadlock
– Nuove keyword nei linguaggi in modo da strutturare in modo
organico il locking nel sorgente (design by contract)
– Nuovi costrutti nei linguaggi che fungano da alternativa al locking
(Transactional Memory, Concurs)
Domande?
Scarica

thread pool - makesimple