Tendo a rispondere a molte domande relative al multithreading e spesso vedo la stessa domanda di base posta in vari modi. Presenterò i problemi più comuni come li ho visti nel corso degli anni e spiegherò come le nuove tecnologie hanno reso più facile la risoluzione di questi problemi.
Chiusura sopra la variabile del ciclo
Questo non è un problema specifico di threading, ma l'uso di infilare ingrandisce definitivamente il problema. C# 5.0 risolve questo problema per il ciclo foreach
creando una nuova variabile per ogni iterazione. Non sarà più necessario creare una variabile speciale per le chiusure di espressioni lambda. Sfortunatamente, il ciclo for
dovrà ancora essere gestito con una variabile di cattura speciale.
In attesa di compiti asincroni per completare
NET 4.0 ha introdotto la classe CountdownEvent
che incapsula un sacco della logica necessaria per attendere il completamento di molte attività. La maggior parte degli sviluppatori junior utilizzava le chiamate Thread.Join
o una singola chiamata WaitHandle.WaitAll
. Entrambi hanno problemi di scalabilità. Il vecchio schema prevedeva l'utilizzo di un singolo ManualResetEvent
e lo segnalava quando un contatore raggiungeva lo zero. Il contatore è stato aggiornato utilizzando la classe Interlocked
. CountdownEvent
rende questo schema molto più semplice. Ricordatevi solo di trattare il vostro principale come un lavoratore per evitare quella sottile condizione di razza che può verificarsi se un lavoratore finisce prima che tutti i lavoratori siano stati accodati.
.NET 4.0 ha introdotto anche la classe Task
che può avere attività figlio incatenate da esso tramite TaskCreationOptions.AttachedToParent
. Se chiami Task.Wait
su un genitore, attenderà il completamento di tutte le attività figlio.
produttore-consumatore
.NET 4.0 ha introdotto la classe BlockingCollection
che agisce come una coda normale eccetto che può bloccare quando la raccolta è vuota. È possibile mettere in coda un oggetto chiamando Add
e deselezionare un oggetto chiamando Take
. Take
blocchi finché un articolo non è disponibile. Ciò semplifica considerevolmente la logica produttore-consumatore. Prima era il caso che gli sviluppatori stessero cercando di scrivere la propria classe di code di blocco. Ma se non sai cosa stai facendo, puoi davvero rovinare tutto ... male. In effetti, per un periodo di tempo prolungato, Microsoft ha avuto un esempio di coda di blocco nella documentazione MSDN, anch'essa gravemente danneggiata. Per fortuna, da allora è stato rimosso.
Aggiornamento UI con il progresso thread di lavoro
L'introduzione di BackgroundWorker
fatto scorporo attività in background da un'applicazione WinForm molto più facile per gli sviluppatori alle prime armi. Il vantaggio principale è che è possibile chiamare ReportProgress
dal gestore di eventi DoWork
ei gestori di eventi ProgressChanged
verranno automaticamente sottoposti a marshalling sul thread dell'interfaccia utente. Ovviamente, chiunque tenga traccia delle mie risposte su SO sa come mi sento riguardo le operazioni di marshalling (via Invoke
o simili) come soluzione per aggiornare l'interfaccia utente con semplici informazioni sull'avanzamento. Ci strappo tutto il tempo perché è generalmente un approccio terribile. BackgroundWorker
impone ancora lo sviluppatore in un modello push (tramite operazioni di marshalling in background), ma almeno lo fa dietro le quinte.
L'inelegance di Invoke
Tutti sappiamo che un elemento dell'interfaccia utente può accedere solo dal thread UI. Ciò significava in genere che uno sviluppatore doveva utilizzare operazioni di marshalling tramite ISynchronizeInvoke
, DispatcherObject
o SynchronizationContext
per trasferire il controllo sul thread dell'interfaccia utente. Ma ammettiamolo. Queste operazioni di marshalling sembrano brutte. Task.ContinueWith
ha reso questo un po 'più elegante, ma la vera gloria va a await
come parte del nuovo modello di programmazione asincrona del C# 5. await
può essere utilizzato per attendere il completamento di Task
in modo tale che il controllo di flusso venga temporaneamente interrotto mentre l'attività è in esecuzione e quindi restituita proprio nel punto corretto nel contesto di sincronizzazione. Non c'è niente di più elegante e soddisfacente dell'uso di await
in sostituzione di tutte quelle chiamate Invoke
.
Programmazione parallela
vedo spesso domande chiedendo come le cose possono accadere in parallelo. Il vecchio modo era creare pochi thread o usare ThreadPool
. .NET 4.0 ha utilizzato TPL e PLINQ. La classe Parallel
è un ottimo modo per ottenere le iterazioni di un ciclo in parallelo. E il numero AsParallel
di PLINQ è un altro lato della stessa medaglia per il vecchio LINQ semplice. Queste nuove funzionalità TPL semplificano notevolmente questa categoria di programmazione multithread.
.NET 4.5 introduce la libreria TPL Data Flow. È destinato a rendere elegante un problema di programmazione parallela altrimenti complesso. Estrae le classi in blocchi. Possono essere blocchi di destinazione o blocchi di origine. I dati possono fluire da un blocco all'altro. Ci sono molti blocchi diversi tra cui BufferBlock<T>
, BroadcastBlock<T>
, ActionBlock<T>
, ecc. Che fanno cose diverse. E, naturalmente, l'intera libreria sarà ottimizzata per l'utilizzo con le nuove parole chiave async
e await
. È un nuovo ed entusiasmante insieme di lezioni che a mio avviso prenderanno lentamente piede.
cessazione Graceful
Come si ottiene un filo di smettere? Vedo molto questa domanda. Il modo più semplice è chiamare Thread.Abort
, ma sappiamo tutti i pericoli di farlo ... Spero. Ci sono molti modi per farlo in sicurezza. .NET 4.0 ha introdotto un concetto più unificato chiamato cancellazione via CancellationToken
e CancellationTokenSource
. Le attività in background possono eseguire il sondaggio IsCancellationRequested
o semplicemente chiamare ThrowIfCancellationRequested
in punti sicuri per interrompere con garbo qualunque lavoro stessero facendo. Altre discussioni possono chiamare Cancel
per richiedere la cancellazione.
I delegati e gli eventi non hanno nulla a che fare con il threading e non sono specifici per .NET 4.5. Per favore sii più chiaro, dato che non posso dire di cosa stai parlando. –
@JohnSaunders I delegati asincroni rendono più semplice il threading; vedi http://msdn.microsoft.com/en-us/library/22t547yb.aspx – LamonteCristo
Hai notato che l'uso asincrono dei delegati è stato in .NET dalla versione 1.0? –