Elaborazione parallela

Tutto si riduce alla classe SynchronizationContext

Stephen Cleary

La programmazione multithreading può rivelarsi alquanto difficile e comporta l'apprendimento di una vasta gamma di concetti e strumenti. Per offrire un supporto, Microsoft .NET Framework fornisce la classe SynchronizationContext. Sfortunatamente, molti sviluppatori non sono neanche a conoscenza di questo utile strumento.

A prescindere dalla piattaforma, sia essa ASP.NET, Windows Forms, Windows Presentation Foundation (WPF), Silverlight o altre, tutti i programmi .NET includono il concetto di SynchronizationContext e tutti i programmatori multithreading possono usufruire dei vantaggi derivanti dalla conoscenza e dall'applicazione di questa classe.

Quando utilizzare la classe SynchronizationContext

I programmi multithreading esistono da molto prima dell'avvento della tecnologia .NET Framework. In tali programmi spesso era necessario passare un'unità di lavoro da un thread a un altro. I programmi Windows erano incentrati su cicli di messaggi e molti programmatori utilizzavano tale coda incorporata per scambiare le unità di lavoro. Per ciascun programma multithreading per il quale era necessario utilizzare la coda di messaggi di Windows in questo modo, occorreva definire un messaggio e una convenzione Windows personalizzati per la gestione.

Quando fu rilasciato .NET Framework, questo modello comune fu standardizzato. All'epoca, l'unico tipo di applicazione GUI supportato da .NET era Windows Form. Tuttavia, i progettisti del framework programmarono altri modelli e svilupparono una soluzione generica: ISynchronizeInvoke.

L'idea alla base di ISynchronizeInvoke è che un thread di "origine" consente di accodare un delegato a un thread di "destinazione" e facoltativamente resta in attesa del completamento del delegato. ISynchronizeInvoke fornì inoltre una proprietà per stabilire se il codice corrente era già in esecuzione nel thread di destinazione; nel qual caso, l'accodamento del delegato sarebbe stato superfluo. Windows Form offriva l'unica implementazione di ISynchronizeInvoke e venne sviluppato un modello per la progettazione di componenti asincroni che soddisfacessero tutti gli sviluppatori.

La versione 2.0 di .NET Framework conteneva molte modifiche sostanziali. Uno dei principali miglioramenti è stata l'introduzione di pagine asincrone nell'architettura ASP.NET. Prima del rilascio di .NET Framework 2.0, per ciascuna richiesta ASP.NET era necessario un thread fino al completamento della richiesta stessa. Ciò si rivelò un modo inefficiente di utilizzare i thread, poiché la creazione di una pagina Web spesso dipende dalle query e dalle chiamate database ai servizi Web e il thread per la gestione di tale richiesta avrebbe dovuto attendere fino al completamento di tutte le operazioni. Con le pagine asincrone, il thread per la gestione della richiesta avrebbe potuto avviare ciascuna delle operazioni e tornare al pool di thread ASP.NET; al completamento delle operazioni, un altro thread del pool di thread ASP.NET avrebbe completato la richiesta.

Tuttavia, il modello ISynchronizeInvoke non si rivelò adatto all'architettura delle pagine asincrone ASP.NET. I componenti asincroni sviluppati mediante il modello ISynchronizeInvoke non avrebbero funzionato correttamente all'interno delle pagine ASP.NET perché le pagine asincrone ASP.NET non sono sono associate a un singolo thread. Invece di accodare i processi al thread originale, per le pagine asincrone è necessario mantenere solo un conteggio delle operazioni rimanenti per stabilire quando è possibile completare la richiesta della pagina. Dopo un lungo studio e un'attenta progettazione, il modello ISynchronizeInvoke fu sostituito da SynchronizationContext.

Il concetto di SynchronizationContext

ISynchronizeInvoke soddisfaceva due esigenze: la possibilità di stabilire se fosse necessaria la sincronizzazione e di accodare un'unità di lavoro da un thread a un altro. SynchronizationContext fu progettato con l'intento sostituire ISynchronizeInvoke, ma al completamento del processo di processo di progettazione si rivelò effettivamente il sostituto naturale.

Un aspetto di SynchronizationContext è la capacità di fornire un modo per accodare un'unità di lavoro a un contesto. Tenere presente che questa unità di lavoro è accodata a un contesto piuttosto che a un thread specifico. Tale distinzione è importante, poiché molte implementazioni di SynchronizationContext non sono basate su un singolo thread specifico. SynchronizationContext non include un meccanismo per stabilire se sia necessaria la sincronizzazione perché ciò non è sempre possibile.

Un altro aspetto di SynchronizationContext è il fatto che ciascun thread dispone di un contesto "corrente", il quale non deve essere necessariamente univoco; la relativa istanza di contesto può essere condivisa con altri thread. È possibile modificare il relativo contesto di un thread ma si tratta di una condizione piuttosto rara.

Un terzo aspetto di SynchronizationContext è la capacità di mantenere un conteggio delle operazioni asincrone rimanenti, che consente di utilizzare le pagine asincrone ASP.NET e qualsiasi altro host che necessiti questo tipo di conteggio. Nella maggior parte dei casi, il conteggio viene incrementato quando viene acquisito il valore corrente di SynchronizationContext, mentre viene decrementato quando il valore acquisito di SynchronizationContext viene utilizzato per accodare una notifica di completamento al contesto.

Esistono altri aspetti di SynchronizationContext, ma sono meno importanti per la maggior parte dei programmatori. Gli aspetti più importanti sono illustrati nella Figura 1.

Figura 1 Aspetti dell'API SynchronizationContext

// The important aspects of the SynchronizationContext APIclass SynchronizationContext

{

  // Dispatch work to the context.

  void Post(..); // (asynchronously)

  void Send(..); // (synchronously)

  // Keep track of the number of asynchronous operations.

  void OperationStarted();

  void OperationCompleted();

  // Each thread has a current context.

  // If "Current" is null, then the thread's current context is


  // "new SynchronizationContext()", by convention.

  static SynchronizationContext Current { get; }

  static void SetSynchronizationContext(SynchronizationContext);
}

Le implementazioni di SynchronizationContext

Il contesto "effettivo" dell'API SynchronizationContext non è chiaramente definito. Per framework e host diversi è possibile definire liberamente il relativo contesto. La comprensione di tali diverse implementazioni e relative limitazioni chiarisce esattamente le garanzie offerto dal concetto di SynchronizationContext. Di seguito illustrerò brevemente alcune di queste implementazioni.

WindowsFormsSynchronizationContext (System.Windows.Forms.dll: System.Windows.Forms) le applicazioni Windows Form consentono di creare e installare un WindowsFormsSynchronizationContext come contesto corrente per tutti i thread che consentono la creazione dei controlli dell'interfaccia utente. In SynchronizationContext vengono utilizzati i metodi di ISynchronizeInvoke in un controllo dell'interfaccia utente che consente il passaggio dei delegati al ciclo di messaggi Win32 sottostante. Il contesto di WindowsFormsSynchronizationContext è un singolo thread dell'interfaccia utente.

Tutti i delegati accodati aWindowsFormsSynchronizationContext vengono eseguiti contemporaneamente da un thread specifico dell'interfaccia utente nell'ordine di accodamento. L'implementazione corrente consente di creare un WindowsFormsSynchronizationContext per ciascun thread dell'interfaccia utente.

DispatcherSynchronizationContext (WindowsBase.dll: System.Windows.Threading) le applicazioni WPF e Silverlight utilizzano un DispatcherSynchronizationContext, che consente di accodare i delegati al dispatcher del thread dell'interfaccia utente con un livello di priorità "Normale". SynchronizationContext viene installato come contesto corrente quando un thread avvia il relativo ciclo dispatcher mediante la chiamata al metodo Dispatcher.Run. Il contesto di DispatcherSynchronizationContext è un singolo thread dell'interfaccia utente.

Tutti i delegati accodati aDispatcherSynchronizationContext vengono eseguiti contemporaneamente da un thread specifico dell'interfaccia utente nell'ordine di accodamento. L'implementazione corrente consente di creare un DispatcherSynchronizationContext per ciascuna finestra superiore, anche se tutte condividono lo stesso dispatcher di base.

(ThreadPool) SynchronizationContext predefinito (mscorlib.dll: System.Threading) Il SynchronizationContext predefinito è un oggetto SynchronizationContext creato in modo predefinito. Per convenzione, se il SynchronizationContext di un thread è null, include implicitamente un SynchronizationContext predefinito.

Il SynchronizationContext predefinito consente di accodare i relativi delegati asincroni all'oggetto ThreadPool, ma consente l'esecuzione dei delegati direttamente nel thread che esegue la chiamata. Pertanto, il relativo contesto include tutti i thread dell'oggetto ThreadPool nonché tutti i thread che eseguono la chiamata al metodo Send. Il contesto "mutua" i thread che eseguono la chiamata al metodo Send, associandoli al relativo contesto fino al completamento del delegato. In tal senso, il contesto predefinito può includere qualsiasi thread nel processo.

Il SynchronizationContext viene applicato ai thread dell'oggetto ThreadPool salvo nel caso in cui il codice sia ospitato da ASP.NET. Il SynchronizationContext viene inoltre applicato in modo implicito ai thread figlio espliciti (istanze della classe Thread) salvo nel caso in cui per il thread figlio non venga impostato un SynchronizationContext personalizzato. Pertanto, nelle applicazioni dell'interfaccia utente sono presenti due contesti di sincronizzazione: il SynchronizationContext dell'interfaccia utente relativo al thread dell'interfaccia utente stessa e il SynchronizationContext predefinito relativo ai thread dell'oggetto ThreadPool.

Molti componenti asincroni basati su eventi non funzionano come previsto con il SynchronizationContext predefinito. Un famigerato esempio è rappresentato da un'applicazione dell'interfaccia utente in cui un oggetto BackgroundWorker avvia un altro oggetto BackgroundWorker. Ciascun oggetto BackgroundWorker acquisisce e utilizza il SynchronizationContext del thread che esegue la chiamata al metodo RunWorkerAsync e successivamente esegue il relativo evento RunWorkerCompleted in tale contesto. Nel caso di un singolo BackgroundWorker, si tratta solitamente di un SynchronizationContext basato su interfaccia utente; pertanto l'evento RunWorkerCompleted viene eseguito nel contesto dell'interfaccia utente acquisita dal metodo RunWorkerAsync (vedere la Figura 2).

image: A Single BackgroundWorker in a UI Context

Figura 2 Singolo oggetto BackgroundWorker in un contesto dell'interfaccia utente

Tuttavia, se l'oggetto BackgroundWorker avvia un altro oggetto BackgroundWorker dal relativo gestore DoWork, il BackgroundWorker non consente l'acquisizione del SynchronizationContext dell'interfaccia utente. Il gestore DoWork viene eseguito da un thread dell'oggetto ThreadPool con il SynchronizationContext predefinito. In tal caso, il metodo RunWorkerAsync consentirà l'acquisizione del SynchronizationContext predefinito e l'esecuzione del relativo evento RunWorkerCompleted in un thread dell'oggetto ThreadPool invece che in un threa dell'interfaccia utente (vedere la Figura 3).

image: Nested BackgroundWorkers in a UI Context

Figura 3 Oggetti BackgroundWorker nidificati in un contesto dell'interfaccia utente

Per impostazione predefinita, tutti i thread inclusi in applicazioni console e servizi di Windows Services dispongono solo del SynchronizationContext predefinito. Ciò comporta il non corretto funzionamento di alcuni componenti asincroni basati su eventi. Una soluzione al problema consiste nel creare un thread figlio esplicito e nell'installare un SynchronizationContext nello stesso thread per poter fornire un contesto per tali componenti. L'implementazione di SynchronizationContext non rientra nell'ambito del presente articolo. Tuttavia, è possibile utilizzare la classe ActionThread della libreria Nito.Async (nitoasync.codeplex.com) come implementazione generica del SynchronizationContext.

AspNetSynchronizationContext (System.Web.dll: System.Web [classe interna]) L'ASP.NET SynchronizationContext viene installato in thread appartenenti a pool di thread durante l'esecuzione del codice della pagina. Quando un delegato viene accodato a un AspNetSynchronizationContext accodato, ripristina l'identità e la lingua della pagina originale, quindi esegue il delegato direttamente. Il delegato viene richiamato direttamente anche anche viene accodato in modo "asincrono" dal metodo Post che esegue la chiamata.

Concettualmente, il contesto di AspNetSynchronizationContext è complesso. Durante la durata di una pagina asincrona, il contesto viene avviato con un solo thread dal pool di thread ASP.NET. Dopo l'avvio delle richieste asincrone, il contesto non include alcun thread. Al completamento delle richieste asincrone, i thread appartenenti al pool di thread che eseguono le routine di completamento accedono al contesto. Potrebbe trattarsi degli stessi thread che hanno inoltrato le richieste, ma più probabilmente si tratta di tutti i thread liberi al momento del completamento delle operazioni.

Se vengono completate più operazioni contemporaneamente per la stessa applicazione, AspNetSynchronizationContext consentirà che vengano eseguite singolarmente. Possono essere eseguite in qualsiasi thread, ma quest'ultimo avrà l'identità e la lingua della pagina originale.

Un esempio comune è rappresentato da un oggetto WebClient utilizzato all'interno di una pagina Web asincrona. Il metodo DownloadDataAsync acquisirà il SynchronizationContext corrente, quindi eseguirà il relativo evento DownloadDataCompleted in tale contesto. Quando inizia l'esecuzione della pagina, in ASP.NET verrà allocato uno dei thread per l'esecuzione del codice contenuto nella pagina. È possibile che nella pagina venga chiamato il metodo DownloadDataAsync e ne venga restituito il risultato; in ASP.NET viene mantenuto un conteggio delle operazioni asincrone rimanenti e dello stato di completamento della pagina. Una volta scaricati i dati richiesti nell'oggetto WebClient, verrà inviata una notifica in un thread del pool di thread, il quale genererà un evento DownloadDataCompleted nel contesto acquisito. Il contesto rimarrà nello stesso thread, ma garantirà l'esecuzione del gestore eventi con l'identità e la lingua corretti.

Osservazioni sulle implementazioni di SynchronizationContext

SynchronizationContext offre un metodo per la creazione di componenti potenzialmente compatibili con molti framework differenti. Gli oggetti BackgroundWorker e WebClient sono due esempi completamente interscambiabili tra applicazioni console, Windows Form, WPF, Silverlight e ASP.NET. Tuttavia, è necessario tenere presente alcuni aspetti durante la progettazione di componenti riutilizzabili di questo tipo.

In generale, le implementazioni di SynchronizationContext non sono paragonabili in termini di uguaglianza. Ciò significa che non esiste alcun equivalente del metodo ISynchronizeInvoke.InvokeRequired. Tuttavia, non si tratta di un aspetto così negativo; il codice sarà più chiaro e semplice da verificare poiché verrà sempre eseguito nell'ambito di un contesto noto invece di continui tentativi di utilizzo di più contesti.

Non tutte le implementazioni di SynchronizationContext garantiscono l'ordine di esecuzione dei delegati o la sincronizzazione di questi ultimi. Le implementazioni di SynchronizationContext basate su interfaccia utente soddisfano tali condizioni, mentre l'implementazione di SynchronizationContext in ambiente ASP.NET consente solo la sincronizzazione. Il SynchronizationContext predefinito non garantisce l'ordine di esecuzione né la sincronizzazione.

Non esiste una corrispondenza diretta tra le istanze e i thread di SynchronizationContext. Il WindowsFormsSynchronizationContext presenta effettivamente una corrispondenza diretta con i thread (fintantoché non viene richiamato il metodo SynchronizationContext.CreateCopy), ma ciò non vale per tutte le altre implementazioni. In generale, è meglio non dare per scontato che qualsiasi istanza del contesto possa essere eseguita in qualsiasi thread specifico.

Infine, il metodo SynchronizationContext.Post non è necessariamente asincrono. Nella maggior parte delle implementazioni è effettivamente asincrono, tuttavia l'implementazione AspNetSynchronizationContext rappresenta un'eccezione degna di nota. Ciò potrebbe comportare problemi di reentrancy. Un riepilogo di queste diverse implementazioni è osservabile nella Figura 4.

Figura 4 Riepilogo delle implementazioni di SynchronizationContext

  Thread specifico utilizzato per eseguire i delegati Esclusiva (i delegati vengono eseguiti singolarmente) Ordinata (i delegati vengono eseguiti in ordine di accodamento) Il metodo Send può richiamare il delegato direttamente Il metodo Post può richiamare il delegato direttamente
Windows Form Se chiamato dal thread dell'interfaccia utente Mai
WPF/Silverlight Se chiamato dal thread dell'interfaccia utente Mai
Predefinito No No No Sempre Mai
ASP.NET No No Sempre Sempre

AsyncOperationManager e AsyncOperation

Le classi AsyncOperationManager e AsyncOperation in .NET Framework sono wrapper semplici dell'astrazione di SynchronizationContext. La classe AsyncOperationManager consente di acquisire il SynchronizationContext corrente la prima volta in cui crea una classe AsyncOperation, sostituendo il SynchronizationContext predefinito se quello corrente è null. La classe AsyncOperation consente di inviare delegati in modo asincrono al SynchronizationContext acquisito.

La maggior parte dei componenti asincroni consentono di utilizzare le classi AsyncOperationManager e AsyncOperation nella propria implementazione. Tali componenti funzionano correttamente per operazioni asincrone con un punto di completamento definito, ovvero l'operazione asincrona inizia in un punto e termina in un altro con un evento. È possibile che altre notifiche asincrone non dispongano di un punto di completamento definito; potrebbe trattarsi di un tipo di sottoscrizione che inizia in un punto e continua in modo indefinito. Per questo tipo di operazioni, SynchronizationContext può essere acquisito e utilizzato direttamente.

Per i nuovi componenti non deve essere utilizzato il modello asincrono basato su eventi. Visual Studio Async Community Technology Preview (CTP) include un documento in cui viene descritto il modello asincrono basato su attività e in cui i componenti restituiscono oggetti Task e Task<TResult> invece di generare eccezioni tramite SynchronizationContext. Le API basate su attività rappresentano il futuro della programmazione asincrona in .NET.

Esempi di supporto delle librerie per SynchronizationContext

Per semplici componenti quali gli oggetti BackgroundWorker e WebClient è implicitamente possibile eseguirne il porting, nascondendo l'acquisizione e l'utilizzo di SynchronizationContext. Molte librerie comportano un utilizzo più visibile di SynchronizationContext. Con l'esposizione delle API tramite SynchronizationContext, le librerie non solo ottengono un'indipendenza dal framework, ma forniscono anche prospettive di estendibilità per gli utenti finali avanzati.

Oltre alle librerie che illustrerò ora, il SynchronizationContext corrente è considerato parte dell'ExecutionContext. Qualsiasi sistema che acquisisca l'ExecutionContext di un thread, acquisisce anche il SynchronizationContext corrente. Quando viene ripristinato l'ExecutionContext, viene solitamente ripristinato anche il SynchronizationContext.

Windows Communication Foundation (WCF):UseSynchronizationContext WCF include due attributi utilizzati per configurare il comportamento del server e dei client: ServiceBehaviorAttribute e CallbackBehaviorAttribute. Entrambi gli attributi presentano una proprietà booleana: UseSynchronizationContext. Il valore predefinito di tale attributo è true, il che significa che il SynchronizationContext corrente viene acquisito quando viene creato il canale di comunicazione e il SynchronizationContext acquisito viene utilizzato per accodare i metodi di contratto.

In genere, tale comportamento rappresenta esattamente ciò che occorre: per i server viene utilizzato il SynchronizationContext predefinito e per i callback del client viene utilizzato il SynchronizationContext dell'interfaccia utente appropriato. Tuttavia, ciò potrebbe causare problemi ove si utilizzi la reentrancy, ovvero un client che richiama un metodo del server che a propria volta richiama un callback del client. In questo e in altri casi simili, in WCF è possibile disattivare l'utilizzo automatico del SynchronizationContext impostando UseSynchronizationContext su false.

Questa è solo una breve descrizione del modo in cui viene utilizzato il SynchronizationContext in WCF. Per ulteriori informazioni, vedere l'articolo intitolato "Contesti di sincronizzazione in WCF" (msdn.microsoft.com/magazine/cc163321) nel numero di novembre 2007 di MSDN Magazine.

Windows Workflow Foundation (WF): WorkflowInstance.SynchronizationContext Negli host WF originariamente veniva utilizzato l'oggetto WorkflowSchedulerService e tipi derivati per controllare il modo in cui pianificare le attività del flusso di lavoro in thread. Parte dell'aggiornamento di .NET Framework 4 includeva la proprietà SynchronizationContext nella classe WorkflowInstance e nella relativa classe derivata WorkflowApplication.

È possibile impostare il SynchronizationContext direttamente se il processo di hosting consente di creare una classe WorkflowInstance personalizzata. SynchronizationContext viene utilizzato anche dal metodo WorkflowInvoker.InvokeAsync, che consente di acquisire il SynchronizationContext corrente e di passarlo alla relativa classe WorkflowApplication interna. Tale SynchronizationContext viene inoltre utilizzato per inviare l'evento di completamento del flusso di lavoro nonché le attività del flusso di lavoro.

Task Parallel Library (TPL): TaskScheduler.FromCurrentSynchronizationContext e CancellationToken.Register Nella libreria TPL vengono utilizzati gli oggetti attività come unità di lavoro che vengono eseguiti tramite un oggetto TaskScheduler. L'oggetto TaskScheduler predefinito funziona come il SynchronizationContext predefinito, accodando le attività all'oggetto ThreadPool. Esiste un altro oggetto TaskScheduler fornito dalla coda TPL che consente di accodare le attività a un SynchronizationContext. È possibile aggiungere report di stato con aggiornamenti dell'interfaccia utente mediante un'attività nidificata, come mostrato nella Figura 5.

Figura 5 Report di stato con aggiornamenti dell'interfaccia utente

private void button1_Click(object sender, EventArgs e)
{
  // This TaskScheduler captures SynchronizationContext.Current.
  TaskScheduler taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
  // Start a new task (this uses the default TaskScheduler, 
  // so it will run on a ThreadPool thread).
  Task.Factory.StartNew(() =>
  {
    // We are running on a ThreadPool thread here.

  
    ; // Do some work.


  // Report progress to the UI.
    Task reportProgressTask = Task.Factory.StartNew(() =>
      {
        // We are running on the UI thread here.

        ; // Update the UI with our progress.
      },
      CancellationToken.None,
      TaskCreationOptions.None,
      taskScheduler);
    reportProgressTask.Wait();
  
    ; // Do more work.
  });
}

La classe CancellationToken viene utilizzata per qualsiasi tipo di annullamento in .NET Framework 4. Per integrarla con i moduli di annullamento esistenti, la classe consente la registrazione di un delegato da richiamare alla richiesta di annullamento. Una volta registrato il delegato, sarà possibile passare il SynchronizationContext. Quando viene richiesto l'annullamento, la classe CancellationToken consente l'accodamento del delegato al SynchronizationContext invece di eseguirlo direttamente.

Microsoft Reactive Extensions (Rx): ObserveOn, SubscribeOn e SynchronizationContextScheduler Rx è una libreria che consente di considerare gli eventi come flussi di dati. L'operatore ObserveOn consente di accodare gli eventi tramite un SynchronizationContext e l'operatore SubscribeOn consente di accodare le sottoscrizioni a tali eventi tramite un SynchronizationContext. L'operatore ObserveOn viene comunemente utilizzato per aggiornare l'interfaccia utente con eventi in ingresso, mentre l'operatore SubscribeOn viene impiegato per utilizzare gli eventi generati dagli oggetti dell'interfaccia utente.

La libreria Rx include anche di un mezzo interno di accodamento delle unità di lavoro: l'interfaccia IScheduler. La libreria Rx include l'interfaccia SynchronizationContextScheduler, un'implementazione dell'interfaccia IScheduler che consente la sincronizzazione con un SynchronizationContext.

Visual Studio Async CTP: punto di attesa, ConfigureAwait, SwitchTo ed EventProgress<T> Il supporto di Visual Studio per le trasformazioni asincrone del codice è stato annunciato in occasione della Microsoft Professional Developers Conference 2010. Per impostazione predefinita, il SynchronizationContext predefinito viene acquisito in un punto di attesa e viene utilizzato per riprendere l'esecuzione dopo l'attesa (più precisamente, consente di acquisire il SynchronizationContext corrente finché non raggiunge un valore null, nel qual caso acquisisce il valore dell'oggetto TaskScheduler):

private async void button1_Click(object sender, EventArgs e)
{
  // SynchronizationContext.Current is implicitly captured by await.
  var data = await webClient.DownloadStringTaskAsync(uri);

  // At this point, the captured SynchronizationContext was used to resume
  // execution, so we can freely update UI objects.
}

Il metodo ConfigureAwait offre un modo per evitare il comportamento di acquisizione del SynchronizationContext predefinito; passando il valore false per il parametro flowContext viene impedito l'utilizzo del SynchronizationContext per riprendere l'esecuzione dopo l'attesa. Esiste anche un metodo di estensione nelle istanze di SynchronizationContext denominato SwitchTo che consente a qualsiasi metodo asincrono di passare a un SynchronizationContext diverso richiamando il metodo SwitchTo e attendendone il risultato.

Visual Studio Async CTP introduce un modello comune per aggiungere un report di stato relativo alle operazioni asincrone: l'interfaccia IProgress<T> e la relativa implementazione EventProgress<T>. Questa classe consente di acquisire il SynchronizationContext corrente quando viene creato e di generare il relativo evento ProgressChanged in tale contesto.

Oltre a tale supporto, i metodi asincroni che restituiscono un valore void incrementeranno il conteggio delle operazioni asincrone all'inizio e lo decrementeranno alla fine. Tale comportamento consente ai metodi asincroni che restituiscono un valore void di funzionare come operazioni asincrone di livello superiore.

Limitazioni e garanzie

La comprensione del funzionamento del SynchronizationContext è utile per qualsiasi programmatore. Nei componenti multi-framework esistenti viene utilizzato per la sincronizzazione degli eventi. Nelle librerie può essere esposto per consentire una flessibilità avanzata. Lo sviluppatore esperto che comprende le limitazioni e le garanzie del SynchronizationContext non avrà problemi a scrivere e utilizzare tali classi.

Stephen Cleary nutre interesse nel multithreading sin dalla prima volta che ne ha sentito parlare. Ha realizzato diversi sistemi aziendali multitasking critici per clienti importanti, tra cui Syracuse News, R. R. Donnelley e BlueScope Steel. Interviene regolarmente nelle conferenze dei gruppi di utenti .NET, negli eventi BarCamps e Day of .NET nei pressi della sua residenza nel Michigan settentrionale, solitamente su argomenti correlati al multithreading. Gestisce un blog sulla programmazione disponibile all'indirizzo nitoprograms.com.

Un ringraziamento al seguente esperto tecnico per la revisione dell'articolo: Eric Eilebrecht