Paralleles Computing

Hier geht es allein um SynchronizationContext

Stephen Cleary

Multithread-Programmierung kann ziemlich schwierig sein, und es gibt unzählige Konzepte und Tools, die erlernt werden müssen. Um dabei zu helfen, stellt Microsoft .NET Framework die SynchronizationContext-Klasse bereit. Leider wissen viele Entwickler nicht, dass es dieses nützliche Tool gibt.

Unabhängig von der Plattform – ASP.NET, Windows Forms, Windows Presentation Foundation (WPF), Silverlight oder andere – alle .NET-Programme enthalten das Konzept von SynchronizationContext, und alle Multithread-Programmierer können von ihr profitieren.

Die Notwendigkeit von SynchronizationContext

Multithread-Programme gab es bereits vor .NET Framework. Diese Programme setzten häufig voraus, dass ein Thread eine Aufgabeneinheit an einen anderen Thread übergibt. Windows-Programme basierten auf Nachrichtenschleifen. Zahlreiche Programmierer verwendeten diese integrierte Warteschlange, um Aufgabeneinheiten zu übergeben. Jedes Multithread-Programm, das die Windows-Nachrichtenschlange auf diese Weise nutzen wollte, musste eine eigene angepasste Windows-Nachricht und eine Konvention für deren Behandlung definieren.

Als das .NET Framework veröffentlicht wurde, wurde dieses allgemeine Muster standardisiert. Zu diesem Zeitpunkt war Windows Forms der einzige GUI-Anwendungstyp, der von .NET unterstützt wurde. Die Entwickler das Frameworks sahen jedoch andere Modelle voraus und entwickelten daher eine generische Lösung. ISynchronizeInvoke war geboren.

Die Idee von ISynchronizeInvoke ist, dass ein Quellthread einen Stellvertreter in die Warteschlange für einen Zielthread einreihen kann und optional auf den Abschluss des Stellvertreters wartet. ISynchronizeInvoke stellte auch eine Eigenschaft bereit, um festzulegen, ob der aktuelle Code bereits auf dem Zielthread ausgeführt wurde. In diesem Fall wäre die Einreihung des Stellvertreters in die Warteschlange nicht notwendig. Windows Forms stellte die einzige Implementierung von ISynchronizeInvoke bereit. Es wurde ein Muster für das Entwerfen asynchroner Komponenten entworfen, und alle waren zufrieden.

Die Version 2.0 von .NET Framework enthielt zahlreiche umwälzende Änderungen. Eine der wesentlichen Verbesserungen bestand in der Einführung asynchroner Seiten in die ASP.NET-Architektur. Vor .NET Framework 2.0 benötigte jede ASP.NET-Anforderung einen Thread, bis die Anforderung abgeschlossen war. Dies bedeutete eine ineffiziente Nutzung von Threads, da die Erstellung einer Webseite häufig von Datenbankabfragen und Aufrufen für Webdienste abhängig ist. Der Thread, der diese Anforderung bearbeitet, muss auf den Abschluss all dieser Vorgänge warten. Mit asynchronen Seiten konnte der Thread, der die Anforderung bearbeitet, jeden dieser Vorgänge starten und anschließend zum ASP.NET-Threadpool zurückkehren. Nach Abschluss der Prozesse schließt ein anderer Thread aus dem ASP.NET-Pool die Anforderung ab.

ISynchronizeInvoke war jedoch nicht gut für die ASP.NET-Architektur für asynchrone Seiten geeignet. Asynchrone Komponenten, die mittels des ISynchronizeInvoke-Musters entwickelt wurden, funktionierten innerhalb von ASP.NET-Seiten nicht korrekt, da asynchrone ASP-NET-Seiten nicht mit einem einzelnen Thread verknüpft sind. Anstelle Aufgaben in die Warteschlange für den ursprünglichen Thread einzureihen, müssen die asynchronen Seiten nur eine Zählung der ausstehenden Prozesse pflegen, um festzustellen, wann die Seitenanforderung abgeschlossen werden kann. Nach vielen Überlegungen und sorgfältiger Planung wurde ISynchronizeInvoke durch SynchronizationContext ersetzt.

Das Konzept von SynchronizationContext

ISynchronizeInvoke erfüllte zwei Anforderungen: Es stellte fest, ob Synchronisierung notwendig war, und es reihte eine Aufgabeneinheit in die Warteschlange für einen anderen Thread ein. SynchronizationContext wurde entwickelt, um ISynchronizeInvoke zu ersetzen. Nach der Entwurfsphase stellte sich jedoch heraus, dass es sich nicht um eine 1:1-Ersetzung handeln würde.

Ein Aspekt von SynchronizationContext ist die Bereitstellung der Möglichkeit, eine Aufgabeneinheit in die Warteschlange für einen Kontext einzureihen. Beachten Sie, dass sie Aufgabeneinheit für einen Kontext und nicht zu einem bestimmten Thread eingereiht wird. Diese Unterscheidung ist wichtig, da zahlreiche Implementierungen von SynchronizationContext nicht auf einem einzelnen, bestimmten Thread basieren. SynchronizationContext enthält keinen Mechanismus für die Feststellung, ob Synchronisierung notwendig ist, da dies nicht immer möglich ist.

Ein anderer Aspekt von SynchronizationContext ist, dass jeder Thread über einen "aktuellen" Kontext verfügt. Der Kontext eines Threads ist nicht notwendigerweise eindeutig. Die Kontextinstanz kann mit anderen Threads gemeinsam verwendet werden. Ein Thread kann den aktuellen Kontext ändern, dies ist jedoch ziemlich selten.

Ein dritter Aspekt von SynchronizationContext besteht darin, dass es die ausstehenden asynchronen Prozesse zählt. Dies ermöglicht die Verwendung von asynchronen ASP.NET-Seiten und anderen Hosts, die diese Art von Zählung benötigen. In den meisten Fällen wird die Zählung erhöht, wenn der aktuelle Synchronisierungskontext erfasst wird, und erniedrigt, wenn der erfasste Synchronisierungskontext für die Einreihung einer Abschlussbenachrichtigung in den Kontext verwendet wird.

Es gibt weitere Aspekte von SynchronizationContext. Diese sind jedoch für die meisten Programmierer weniger wichtig. Die wichtigsten Aspekte werden in Abbildung 1 gezeigt.

Abbildung 1 Aspekte der SynchronizationContext-API

// 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);
}

Die Implementierung von SynchronizationContext

Der tatsächliche "Kontext" von SynchronizationContext ist nicht eindeutig definiert. Die verschiedenen Frameworks und Hosts können ihren eigenen Kontext definieren. Das Verstehen dieser verschiedenen Implementierungen und deren Begrenzungen verdeutlicht, was das SynchronizationContext-Konzept leistet und was es nicht leistet. Ich behandele nun kurz einige dieser Implementierungen.

WindowsFormsSynchronizationContext (System.Windows.Forms.dll: System.Windows.Forms) Windows Forms-Anwendungen erstellen und installieren WindowsFormsSynchronizationContext als den aktuellen Kontext für alle Threads, die Steuerelemente der Benutzeroberfläche erstellen. SynchronizationContext verwendet die ISynchronizeInvoke-Methoden für ein Benutzeroberflächen-Steuerelement, das die Stellvertreter an die zugrunde liegende Win32-Nachrichtenschleife übergibt. Der Kontext für WindowsFormsSynchronizationContext ist ein einzelner Benutzeroberflächenthread.

Alle Stellvertreter, die für WindowsFormsSynchronizationContext eingereiht sind, werden einzeln ausgeführt. Sie werden von einem spezifischen Benutzeroberflächenthread in der Reihenfolge ausgeführt, in der sie eingereiht wurden. Die aktuelle Implementierung erstellt einen einzigen WindowsFormsSynchronizationContext für jeden einzelnen Benutzeroberflächenthread.

DispatcherSynchronizationContext (WindowsBase.dll: System.Windows.Threading) WPF- und Silverlight-Anwendungen verwenden einen DispatcherSynchronizationContext, der Delegierte für den Dispatcher des Benutzeroberflächenthreads mit normaler Priorität einreiht. SynchronizationContext wird als aktueller Kontext installiert, wenn ein Thread die Dispatcherschleife startet, indem Dispatcher.Run aufgerufen wird. Der Kontext für DispatcherSynchronizationContext ist ein einzelner Benutzeroberflächenthread.

Alle Stellvertreter, die für DispatcherSynchronizationContext eingereiht sind, werden von einem spezifischen Benutzeroberflächenthread in der Reihenfolge ausgeführt, in der sie eingereiht wurden. Die aktuelle Implementierung erstellt einen DispatcherSynchronizationContext für jedes Fenster auf der obersten Ebene, auch wenn diese alle den gleichen zugrunde liegenden Dispatcher verwenden.

Default (ThreadPool) SynchronizationContext (mscorlib.dll: System.Threading) Der voreingestellte Synchronisierungskontext ist ein voreingestellt konstruiertes SynchronizationContext-Objekt. Wenn der aktuelle Synchronisierungskontext eines Threads Null ist, verfügt er konventionsgemäß über einen voreingestellten Synchronisierungskontext.

Der voreingestellte Synchronisierungskontext reiht die asynchronen Stellvertreter in den Threadpool ein, führt die synchronen Stellvertreter jedoch direkt auf dem aufrufenden Thread aus. Daher deckt der Kontext alle Threadpool-Threads sowie alle Threads ab, die "Send" aufrufen. Der Kontext borgt die Threads, die "Send" aufrufen, und bringt diese in den Kontext, bis der Delegierte abgeschlossen ist. Daher kann der vordefinierte Kontext alle Threads im Prozess enthalten.

Der vordefinierte Synchronisierungskontext wird auf die Threadpool-Threads angewendet, es sei denn, der Code wird von ASP.NET gehostet. Der vordefinierte Synchronisierungskontext wird außerdem implizit auf explizite untergeordnete Threads angewendet (Instanzen der Thread-Klasse), es sei denn, der untergeordnete Thread legt einen eigenen Synchronisierungskontext fest. Daher verfügen UI-Anwendungen in der Regel über zwei Synchronisierungskontexte: den Benutzeroberflächen-Synchronisierungskontext und den vordefinierten Synchronisierungskontext, der die Threadpool-Threads abdeckt.

Zahlreiche ereignisbasierte asynchrone Komponenten funktionieren im vordefinierten Synchronisierungskontext nicht wie erwartet. Ein berühmtes Beispiel ist eine Benutzeroberflächenanwendung, in der ein Workerprozess im Hintergrund einen anderen Workerprozess im Hintergrund startet. Jeder Workerprozess im Hintergrund erfasst und verwendet den Synchronisierungskontext des Threads, der RunWorkerAsync aufruft, und führt später das RunWorkerCompleted-Ereignis in diesem Kontext aus. Im Fall eines einzelnen Workerprozesses im Hintergrund ist dies in der Regel ein Benutzeroberflächen-basierter Synchronisierungskontext, sodass RunWorkerCompleted in dem Benutzeroberflächenkontext ausgeführt wird, der von RunWorkerAsync erfasst wird (siehe Abbildung 2).

image: A Single BackgroundWorker in a UI Context

Abbildung 2 Ein einzelner Workerprozess im Hintergrund in einem Benutzeroberflächenkontext

Wenn der Workerprozess im Hintergrund einen anderen Workerprozess im Hintergrund vom DoWork-Handler startet, erfasst der verschachtelte Workerprozess im Hintergrund den Benutzeroberflächen-Synchronisierungskontext nicht. DoWork wird von einem Threadpool mit dem Synchronisierungskontext ausgeführt. In diesem Fall erfasst der verschachtelte RunWorkerAsync den vordefinierten Synchronisierungskontext, sodass dieser RunWorkerCompleted auf einem Threadpool-Thread und nicht auf einem Benutzeroberflächenthread ausführt (siehe Abbildung 3).

image: Nested BackgroundWorkers in a UI Context

Abbildung 3 Verschachtelter Workerprozess im Hintergrund in einem Benutzeroberflächenkontext

Standardmäßig verfügen alle Threads in Konsolenanwendungen und Windows Services nur über den vordefinierten Synchronisierungskontext. Daher kommt es bei einigen ereignisbasierten asynchronen Komponenten zu Fehlern. Eine Lösung hierfür besteht in der Erstellung eines expliziten untergeordneten Threads und der Installation eines Synchronisierungskontexts auf diesem Thread, der anschließend einen Kontext für diese Komponenten bereitstellt. Die Implementierung eines Synchronisierungskontexts ist nicht Thema dieses Artikels. Sie können jedoch die ActionThread-Klasse der Nito.Async-Bibliothek als Synchronisierungskontext-Implementierung für allgemeine Zwecke verwenden (nitoasync.codeplex.com.

AspNetSynchronizationContext (System.Web.dll: System.Web [internal class]) Der ASP.NET-Synchronisierungskontext wird auf den Threadpool-Threads installiert, während diese Seitencode ausführen. Wenn ein Delegierter in einen erfassten ASP.NET-Synchronisierungskontext eingereiht wird, wird die Identität und Kultur der ursprünglichen Seite wiederhergestellt und anschließend der Delegierte direkt ausgeführt. Der Delegierte wird direkt aufgerufen, auch wenn er asynchron eingereiht wurde, indem "Post" aufgerufen wird.

Konzeptionell betrachtet ist der Kontext von AspNetSynchronizationContext komplex. Während des Lebenszyklus einer asynchronen Seite startet der Kontext mit nur einem Thread aus dem ASP.NET-Threadpool. Nach dem Starten der asynchronen Anforderungen enthält der Kontext keine Threads. Wenn die asynchronen Anforderungen abgeschlossen sind, treten die Threadpool-Threads, die deren Abschlussroutinen ausführen, in den Kontext ein. Dies können die gleichen Threads sein, die die Anforderungen initiiert haben. Wahrscheinlicher handelt es sich jedoch um Threads, die zum Zeitpunkt des Abschlusses der Prozesse frei sind.

Wenn mehrere Prozesse für die gleiche Anwendung gleichzeitig abgeschlossen werden, stellt AspNetSynchronizationContext sicher, dass sie einzeln ausgeführt werden. Sie können auf jedem Thread ausgeführt werden. Dieser Thread hat jedoch die Identität und Kultur der ursprünglichen Seite.

Ein häufiges Beispiel ist ein Webclient, der von einer asynchronen Webseite aus verwendet wird. DownloadDataAsync erfasst den aktuellen Synchronisierungskontext und führt später das DownloadDataCompleted-Ereignis in diesem Kontext aus. Wenn die Seite beginnt, ausgeführt zu werden, teilt ASP.NET einen Thread der Ausführung von Code in dieser Seite zu. Die Seite kann DownloadDataAsync aufrufen und anschließend zurückkehren. ASP.NET zählt die ausstehenden asynchronen Prozesse, sodass es weiß, dass die Erstellung der Seite nicht abgeschlossen ist. Wenn das Webclientobjekt die angeforderten Daten heruntergeladen hat, erhält es eine Benachrichtigung auf einem Threadpool-Thread. Dieser Thread ruft DownloadDataCompleted im erfassten Kontext auf. Der Kontext bleibt auf dem gleichen Thread, stellt jedoch sicher, dass der Ereignishandler mit der korrekten Identität und Kultur ausgeführt wird.

Hinweise zu Implementierungen von SynchronizationContext

SynchronizationContext stellt ein Mittel für das Erstellen von Komponenten dar, die in verschiedenen Umgebungen funktionieren. Workerprozess im Hintergrund und Webclient sind zwei Beispiele, die für Windows Forms-, WPF-, Silverlight-, Konsolenanwendungen und ASP.NET-Anwendungen zutreffen. Es gibt jedoch einige Punkte, die beim Entwerfen solcher wiederverwendbarer Komponenten berücksichtigt werden müssen.

Grundsätzlich sind SynchronizationContext-Implementierungen nicht mit Gleichheit vergleichbar. Das bedeutet, dass es keine Entsprechung zu ISynchronizeInvoke.InvokeRequired gibt. Dies jedoch kein wesentlicher Nachteil. Der Code ist klarer und einfacher zu überprüfen, wenn er stets in einem bekannten Kontext ausgeführt und nicht versucht wird, mehrere Kontexte abzudecken.

Nicht alle SynchronizationContext-Implementierungen garantieren die Reihenfolge der Stellvertreterausführung oder die Synchronisierung von Stellvertretern. Die Benutzeroberflächen-basierten SynchronizationContext-Implementierungen erfüllen diese Bedingungen. Der ASP.NET-Synchronisierungskontext stellt jedoch nur die Synchronisierung bereit. Der vordefinierte Synchronisierungskontext garantiert weder die Reihenfolge der Ausführung noch die Synchronisierung.

Es gibt keine 1:1-Entsprechung zwischen den SynchronizationContext-Instanzen und -Threads. Der Windows Forms-Synchronisierungskontext hat eine 1:1-Zuordnung zu einem Thread (solange wie SynchronizationContext.CreateCopy nicht aufgerufen wird). Dies gilt jedoch für keine der anderen Implementierungen. Grundsätzlich ist es am besten, nicht davon auszugehen, dass eine Kontextinstanz auf einem spezifischen Thread ausgeführt wird.

Schließlich ist die SynchronizationContext.Post-Methode nicht notwendigerweise asynchron. Die meisten Implementierungen implementieren sie asynchron, AspNetSynchronizationContext ist jedoch eine Ausnahme. Dies kann zu unerwarteten Neueintrittproblemen führen. Eine Übersicht über die verschiedenen Implementierungen finden Sie in Abbildung 4.

Abbildung 4Übersicht über SynchronizationContext-Implementierungen

  Spezifischer Thread, der für die Ausführung von Stellvertretern verwendet wird Exklusiv (Delegierte werden jeweils einzeln ausgeführt) Reihenfolge (Delegierte werden nach Warteschlangenreihenfolge ausgeführt Senden kann den Stellvertreter direkt aufrufen Veröffentlichen kann den Stellvertreter direkt aufrufen
Windows Forms Ja Ja Ja Wenn durch Benutzeroberflächenthread aufgerufen Nie
WPF/Silverlight Ja Ja Ja Wenn durch Benutzeroberflächenthread aufgerufen Nie
Standard Nein Nein Nein Immer Nie
ASP.NET Nein Ja Nein Immer Immer

AsyncOperationManager und AsyncOperation

Die Klassen AsyncOperationManager und AsyncOperation im .NET Framework sind einfache Wrapper für die SynchronizationContext-Abstraktion. AsyncOperationManager erfasst den aktuellen Synchronisierungskontext, wenn zum ersten Mal ein asynchroner Prozess ausgeführt wird, wobei ein vordefinierter Synchronisierungskontext ersetzt wird, wenn der aktuelle Null ist. AsyncOperation veröffentlicht Delegierte asynchron an den erfassten Synchronisierungskontext.

Die meisten ereignisbasierten asynchronen Komponenten verwenden AsyncOperationManager und AsyncOperation bei der Implementierung. Dies funktioniert gut für asynchrone Prozesse, die über einen definierten Abschlusspunkt verfügen, d. h. wenn der asynchrone Prozess an einem Punkt beginnt und mit einem Ereignis an einem anderen Punkt endet. Andere asynchrone Benachrichtigungen verfügen möglicherweise nicht über einen definierten Abschlusspunkt. Dies kann ein Abonnementtyp sein, der an einem Punkt beginnt und anschließend für unbestimmte Zeit andauert. Für diese Arten von Prozessen kann der Synchronisierungskontext erfasst und direkt verwendet werden.

Neue Komponenten sollten das ereignisbasierte asynchrone Muster nicht verwenden. Die asynchrone Visual Studio Community Technology Preview (CTP) enthält ein Dokument, in das aufgabenbasierte asynchrone Muster beschrieben wird, in dem Komponenten Task- und Task<TResult>-Objekte zurückgeben, anstatt Ereignisse mittels des Synchronisierungskontexts aufzurufen. Aufgabenbasierte APIs sind die Zukunft der asynchronen Programmierung in .NET.

Beispiele für die Bibliotheksunterstützung für SynchronizationContext

Einfache Komponenten wie BackgroundWorker und WebClient sind implizit portierbar und verbergen die Erfassung und Nutzung des Synchronisierungskontexts. Zahlreiche Bibliotheken verwenden SynchronizationContext auf offensichtlichere Weise. Durch die Veröffentlichung von APIs, die SynchronizationContext verwenden, werden Bibliotheken nicht nur vom Framework unabhängig, sondern können fortgeschrittenen Endbenutzern auch einen Erweiterbarkeitspunkt bereitstellen.

Zusätzlich zu den Bibliotheken, die ich nun besprechen werde, muss der aktuelle Synchronisierungskontext als Teil des Ausführungskontexts betrachtet werden. Ein System, das den Ausführungskontext eines Threads erfasst, erfasst den aktuellen Synchronisierungskontext. Wenn der Ausführungskontext wiederhergestellt wird, wird in der Regel auch der Synchronisierungskontext wiederhergestellt.

Windows Communication Foundation (WCF):UseSynchronizationContext WCF hat zwei Attribute, die für die Konfigurierung des Server- und Clientverhaltens verwendet werden: ServiceBehaviorAttribute und CallbackBehaviorAttribute. Beide Attribute haben eine Boole'sche Eigenschaft: UseSynchronizationContext. Der vordefinierte Wert dieses Attributs ist wahr. Dies bedeutet, dass der aktuelle Synchronisierungskontext erfasst wird, wenn der Kommunikationskanal erstellt wird. Dieser Synchronisierungskontext wird für die Einreihung der Contract-Methoden verwendet.

Normalerweise entspricht dieses Verhalten genau dem, was benötigt wird: Server verwenden den vordefinierten Synchronisierungskontext, und Clientcallbacks verwenden den entsprechenden Benutzeroberflächen-Synchronisierungskontext. Dies kann jedoch zu Problemen führen, wenn ein Neueintritt gewünscht wird, wie z. B. ein Client, der eine Servermethode aufruft, die einen Clientcallback aufruft. In diesen und ähnlichen Fällen kann die automatischen Nutzung des Synchronisierungskontexts durch WCF deaktiviert werden, indem UseSynchronizationContext als "false" festgelegt wird.

Dies ist nur eine kurze Beschreibung, wie WCF SynchronizationContext verwendet. Weitere Informationen finden Sie im Artikel "Synchronisierung in WCF" (msdn.microsoft.com/magazine/cc163321) in der Ausgabe vom November 2007 des MSDN-Magazins (möglicherweise in englischer Sprache).

Windows Workflow Foundation (WF): WorkflowInstance.SynchronizationContext WF hostet den ursprünglich verwendeten WorkflowSchedulerService und abgeleitete Typen, um die Planung von Workflowaktivitäten auf Threads zu steuern. Teil des .NET Framework 4-Upgrades ist die SynchronizationContext-Eigenschaft auf der WorkflowInstance-Klasse und der von dieser abgeleiteten WorkflowApplication-Klasse.

Der Synchronisierungskontext kann direkt festgelegt werden, wenn der Hostingprozess seine eigene Workflowinstanz erstellt. SynchronizationContext wird auch von WorkflowInvoker.InvokeAsync verwendet, die den aktuellen SynchronizationContext-Kontext erfasst und an die interne Workflowanwendung übergibt. Dieser Synchronisierungskontext wird anschließend verwendet, um das Workflowabschlussereignis sowie die Workflowaktivitäten zu veröffentlichen.

Task Parallel Library (TPL): TaskScheduler.FromCurrentSynchronizationContext and CancellationToken.Register Die TPL verwendet Aufgabenobjekte als Arbeitseinheiten und führt diese über einen Aufgabenplaner aus. Der vordefinierte Aufgabenplaner funktioniert wie der vordefinierte Synchronisierungskontext, indem die Aufgaben für den Threadpool eingereiht werden. Die TPL-Warteschlange stellt einen weiteren Aufgabenplaner bereit, der die Aufgaben in einen Synchronisierungskontext einreiht. Fortschrittsberichte mit Benutzeroberflächenupdates können mit einer verschachtelten Aufgabe durchgeführt werden, wie in Abbildung 5 gezeigt.

Abbildung 5 Fortschrittsberichte mit Benutzeroberflächenupdates

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.
  });
}

Die CancellationToken-Klasse wird für alle Arten von Abbrüchen in .NET Framework 4 verwendet. Für die Integration mit vorhandenen Formen von Abbrüchen ermöglicht diese Klasse die Registrierung eines Stellvertreters, der aufgerufen wird, wenn ein Abbruch erforderlich ist. Wenn der Delegierte registriert ist, kann ein Synchronisierungskontext übergeben werden. Wenn der Abbruch angefordert wird, reiht CancellationToken den Stellvertreter in den Synchronisierungskontext ein, anstatt ihn direkt auszuführen.

Microsoft Reactive Extensions (Rx): ObserveOn, SubscribeOn und SynchronizationContextScheduler Rx ist eine Bibliothek, die Ereignisse als Datenströme behandelt. Der ObserveOn-Operator reiht Ereignisse über einen Synchronisierungskontext in die Warteschlange ein, und der SubscribeOn-Operator reiht die Abonnements für diese Ereignisse über einen Synchronisierungskontext ein. ObserveOn wird im Allgemeinen für die Aktualisierung der Benutzeroberfläche durch eingehende Ereignisse verwendet, und SubscribeOn wird für die Nutzung von Ereignissen aus Benutzeroberflächenobjekten verwendet.

Rx verfügt außerdem über ein eigenes Verfahren für die Einreihung von Arbeitseinheiten in Warteschlangen: die IScheduler-Schnittstelle. Rx enthält SynchronizationContextScheduler, eine Implementierung von IScheduler, der für einen Synchronisierungskontext einreiht.

Visual Studio Async CTP: await, ConfigureAwait, SwitchTo und EventProgress<T> Die Visual Studio-Unterstützung für asynchrone Codeumwandlungen wurde auf der Microsoft Professional Developers Conference 2010 angekündigt. Standardmäßig wird der aktuelle Synchronisierungskontext an einem Wartepunkt erfasst, und dieser Synchronisierungskontext wird für die Wiederaufnahme nach dem Wartezeitraum verwendet (genauer gesagt, es wird der aktuelle Synchronisierungskontext erfasst, wenn dieser nicht Null ist; in diesem Fall erfasst sie den aktuellen Aufgabenplaner):

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.
}

ConfigureAwait stellt eine Möglichkeit dar, das vordefinierte SynchronizationContext-Erfassungsverhalten zu vermeiden. Die Übergabe von "false" an den flowContext-Parameter verhindert, dass der Synchronisierungskontext für die Wiederaufnahme der Ausführung nach dem Wartezeitraum verwendet wird. Es gibt außerdem eine Erweiterungsmethode für SynchronizationContext-Instanzen namens SwitchTo. Diese ermöglicht jeder asynchronen Methode den Wechsel zu einem anderen Synchronisierungskontext, indem SwitchTo aufgerufen und das Ergebnis abgewartet wird.

Die asynchrone CTP führt ein allgemeines Muster für Fortschrittsberichte aus asynchronen Prozessen ein: die IProgress<T>-Schnittstelle und deren Implementierung EventProgress<T>. Diese Klasse erfasst den aktuellen Synchronisierungskontext, wenn dieser konstruiert wird, und ruft dessen ProgressChanged-Ereignis in diesem Kontext ab.

Zusätzlich zu dieser Unterstützung werden "Void" zurückgebende asynchrone Methoden die Zahl der asynchronen Prozesse erhöhen und bei Abschluss erniedrigen. Dadurch verhalten sich "Void" zurückgebende asynchrone Methoden wie asynchrone Prozesse auf der obersten Ebene.

Einschränkungen und Garantien

Das Verstehen des SynchronizationContext-Konzepts ist für jeden Programmierer nützlich. Vorhandene Framework-übergreifende Komponenten verwenden ihn, um die Ereignisse zu synchronisieren. Bibliotheken können ihn für eine höhere Flexibilität veröffentlichen. Der kenntnisreiche Programmierer, der die Grenzen und die Leistung der SynchronizationContext-Klasse kennt, kann besseren Code erstellen und solche Klassen besser nutzen.

Stephen Cleary hat sich für das Multithreading interessiert, seitdem er von diesem Konzept gehört hat. Er hat zahlreiche geschäftskritische Multitaskingsysteme für große Kunden entwickelt, wie Syracuse News, R. R. Donnelley und BlueScope Steel. Er hält regelmäßig Vorträge für .NET-Benutzergruppen, BarCamps und Day of .NET-Veranstaltungen in der Nähe seines Wohnorts im nördlichen Michigan, in der Regel über Multithreading. Er schreibt einen Programmiererblog unter nitoprograms.com.

Unser Dank gilt dem folgenden technischen Experten für die Durchsicht dieses Artikels: Eric Eilebrecht