Ähsync, ähwait und ÄhmWieWieÄhm?

Asynchrone Programmierung in Windows-8-Anwendungen
Kommentare

Um dem Grundsatz „Fast and fluid“ gerecht zu werden, führen .NET 4.5 und Windows 8 verpflichtend ein asynchrones Programmiermodell ein. In Beispielen sieht dessen Verwendung eigentlich immer ganz einfach aus, doch wer abseits von den vorgegebenen Pfaden erste eigene Schritte unternimmt, stolpert schnell über die Feinheiten. Dieser Artikel möchte in zwei Teilen das Verständnis von asynchronem C#-Quelltext stärken und zeigen, wie gut er sich in XAML-Anwendungen und das bekannte MVVM-Muster einbinden lässt.

Das wohl wichtigste Ziel bei der Architektur von Windows 8 war es, dem Benutzer ein System an die Hand zu geben, das zu jeder Zeit und auch auf leistungsschwachen Geräten alle Eingaben ohne merkliche Wartezeiten umsetzen kann und den Benutzer so nie zum Warten zwingt. Microsoft ist dabei keine Kompromisse eingegangen und bricht mit alten Gewohnheiten. Zuallererst sieht man das am neuen WinRT-API, auf dem alle neuen Anwendungen im Windows-8-Stil aufbauen. WinRT wurde komplett in nativem C++ implementiert und ersetzt auch in C#- und Visual-Basic-Anwendungen den Großteil der verwalteten Bibliotheken aus der .NET Library. Das neue API ist dadurch schneller und vor allem energiesparender als die alte Kombination aus Win32 und .NET. Das ist sehr wichtig für Microsoft, denn nur so kann man mit den Akkulaufzeiten der Mitbewerber konkurrieren.

Das API ergänzt oder ersetzt dabei viele bekannte synchrone Operationen durch neue asynchrone Versionen. Als Richtlinie wurde jede Operation, deren Abarbeitung länger als 50 Millisekunden dauern kann, asynchron gestaltet. Für Außenstehende ist diese Zeitspanne natürlich nur ein Anhaltspunkt ohne Bezug zu irgendeiner echten Hardware, aber die Intention ist deutlich. Doch was bedeutet das Schlagwort „asynchron“ für Windows-8-Anwendungen überhaupt? Im Normalfall wechseln sich eine Anwendung und das User Interface Framework (wie Windows Forms oder WPF) im selben Thread ab und können nicht gleichzeitig arbeiten. Lange Wartezeiten in der Anwendung blockieren dann das User Interface, das in dieser Zeit nicht auf Eingaben durch den Benutzer reagieren kann. Die Anwendung ruckelt. Asynchron bedeutet hier nun, dass potenziell lang andauernde Operationen in einen eigenen Thread im Hintergrund verlegt werden und das User Interface Framework auf diese Weise schon wieder arbeiten kann, noch bevor die Operation in der Anwendung fertig ist. Der Benutzer wird so mit einem User Interface belohnt, das, wie bei der Konkurrenz, jederzeit flüssig auf Eingaben reagiert.

Die praktische Umsetzung von „asynchron“ im Quelltext hatte im Laufe der Zeit unterschiedliche Bedeutungen. In .NET 1.0 gab es das „Asynchronous Programming Model“ (APM), das über die IAsyncResult-Schnittstelle arbeitet. Mit .NET 2.0 wurde das „Event-based Asynchronous Pattern“ eingeführt, das Delegaten als Event Handler verwendet. Mit .NET 4.0 kam dann die „Task Parallel Library“ (TPL). In .NET 4.5 steht „asynchron“ jetzt für das neue „Task-based Asynchronous Pattern“ (TAP). Dabei wurde das Rad aber nicht schon wieder neu erfunden, denn das TAP ist eine Weiterentwicklung bzw. Ergänzung der TPL. APM und EAP gelten damit als veraltet, und TPL und TAP sind der Stand der Dinge.

Dieser Artikel ist für Einsteiger in die Entwicklung von Windows-8-Anwendungen gedacht und will das TAP durch die Beobachtung des Verhaltens von sehr einfachen Beispielen vermitteln. Der vorliegende Teil 1 erklärt die Grundlagen, und Teil 2 wird zeigen, wie diese sich in echte XAML-Anwendungen und das MVVM-Entwurfsmuster einbinden lassen. Es werden keine Kenntnisse der TPL aus .NET 4.0 vorausgesetzt. Alle hier gezeigten Quelltexte und weitere Beispiele können auf www.windowsdeveloper.de heruntergeladen werden.

Neue Schlüsselworte und neue Begriffe

Der C# 5.0 Compiler im .NET Framework 4.5 führt für die Verwendung des TAP zwei neue Schlüsselworte in die Sprache ein, die immer gemeinsam verwendet werden: async und await.

Das Schlüsselwort async kann bei der Definition einer Methode verwendet werden, um auszudrücken, dass die Abarbeitung der Methode potenziell lange dauern kann und sie deswegen bestimmte Unterbrechungspunkte enthält. Solche Methoden bezeichnen wir zur Unterscheidung von „normalen“ oder „synchronen“ als „asynchrone Methoden“ (asynchrone Delegaten und asynchrone Lambda-Ausdrücke können ebenfalls definiert werden, wir bleiben hier der Einfachheit halber aber zunächst bei Methoden). Dabei gilt immer:

Regel 1: Eine asynchrone Methode muss entweder void oder eine Instanz der Typen Task oder Task zurückgeben:

async void Bar1() { ... } async Task Bar2() { ... } async Task Bar3() { ... } 

Das Schlüsselwort async ist dabei nicht Teil der Signatur einer Methode. Das bedeutet, es kann nicht in Schnittstellen oder durch abstrakte oder virtuelle Methoden gefordert werden. Die Verwendung von async bei Methoden ohne Rumpf erzeugt einen Compiler-Fehler („The ‚async‘ modifier can only be used in methods that have a statement body“). Bei der Implementierung von Schnittstellen oder beim Überschreiben von Methoden kann es jederzeit verwendet oder weggelassen werden.

Die genannten Unterbrechungspunkte können immer dort in eine asynchrone Methode eingeführt werden, wo andere Methoden verwendet werden. Das Schlüsselwort await drückt an so einer Stelle aus, dass der Scheduler des Betriebssystems die Anwendung im User Interface Thread unterbrechen und die Kontrolle an das User Interface Framework zurückgeben kann. Nachdem die verwendete Methode mit ihrer Arbeit fertig ist, kehrt der Kontrollfluss an die Anwendung zurück, als wäre er nie woanders gewesen:

await Bar1(); int result = await Bar2();

Wenn wir eine Methode auf diese Weise verwenden, dann rufen wir sie nicht auf, sondern wir „erwarten“ sie. Mit dieser Bezeichnung gilt dann auch:

Regel 2: Eine Methode kann nur dann erwartet werden, wenn sie eine Instanz der Typen Task oder Task zurückgibt und Regel 3: Eine Methode kann nur aus einer asynchronen Methode heraus erwartet werden.

Mit dem Wissen um den Rückgabewert einer „erwartbaren“ Methode kann die Verwendung von Bar1 und Bar2 auch auf die folgende Weise geschrieben werden, die, unter Verwendung der TPL, eine feinere Kontrolle der Tasks ermöglicht:

Task task1 = Bar1(); await task1; Task task2 = Bar2(); int result = await task2;

Abschließend ist noch zu erwähnen, dass eine asynchrone Methode, die keine anderen Methoden erwartet (also ein async ohne ein await) zu einer Compiler-Warnung führt und die als asynchron gedachte Methode als synchrone behandelt wird („This async method lacks ‚await‘ operators and will run synchronously. Consider using the ‚await‘ operator to await non-blocking API calls, or ‚await Task.Run(…)‘ to do CPU-bound work on a background thread.“). Der umgekehrte Fall (ein await ohne ein async) wird durch Regel 3 abgedeckt und führt zu dem genannten Compiler-Fehler.

Kombinationen

Zusammengenommen gilt also: Es gibt synchrone Methoden und asynchrone Methoden sowie nichterwartbare und erwartbare, und von einer dieser beiden Kategorien kann nicht auf die andere geschlossen werden. Für ein Verständnis des TAP ist die bisherige Beschreibung also noch nicht ausreichend. Betrachten wir nun also, wie sich diese Kategorien miteinander kombinieren lassen.

Für eine synchrone/asynchrone Methode Foo, die eine synchrone/asynchrone und erwartbare/nicht-erwartbare Methode Bar verwendet, ergeben sich damit die folgenden relevanten Fälle:

  1. Ein synchrones Foo verwendet ein synchrones und nichterwartbares Bar. Das ist der bekannte synchrone Methodenaufruf (und wer nicht weiß, was hier passiert, der hat sicherlich das falsche Magazin in den Händen). void Foo() { ... Bar(); ... } void Bar() { ... }
  2. Ein synchrones Foo verwendet ein asynchrones und nichterwartbares Bar. Diese Kombination ist der Einstieg in jeden asynchronen Teil eines Call Stacks, denn der Kontrollfluss beginnt irgendwo immer mit einer synchronen Methode. Zum einen, weil bestehender Code aus der alten Welt synchron ist und zum anderen, weil Konstruktoren nicht asynchron sein dürfen (der Compiler-Fehler in diesem Fall lautet „The type or namespace name ‚async‘ could not be found (are you missing a using directive or an assembly reference?)“). void Foo() { ... Bar(); ... } async void Bar() { ... await ... }
  3. Ein asynchrones Foo verwendet ein asynchrones und erwartbares Bar. Diese Kombination kommt in der Mitte von asynchronen Call Stacks vor. Was den Kontrollfluss angeht, verhält sich dieser Fall genauso, als wären Foo und Bar synchron. async void Foo() { ... await Bar(); ... } async Task Bar() { ... await ... }
  4. Ein asynchrones Foo verwendet ein synchrones und erwartbares Bar. Diese Kombination ist das Ende aller asynchronen Call-Stack-Teile. In den allermeisten Fällen wird es sich bei Bar um eine Methode aus der WinRT handeln, zum Beispiel für den Zugriff auf Dateien oder das Internet. Die meisten Beispiele über async und await gehen aus diesem Grund nicht weiter auf solche Methoden ein und laden am Ende einer Abfolge einfach eine Zeichenkette aus dem Netz. Eine eigene Implementierung ist aber dann sinnvoll, wenn CPU-lastige Berechnungen ausgeführt werden sollen, ohne dass die Benutzerschnittstelle blockiert wird, und wir werden gleich sehen, wie das funktioniert. async void Foo() { ... await Bar(); ... } Task Bar() { ... }
  5. Ein Sonderfall ergibt sich, wenn eine erwartbare Methode durch einen klassischen Aufruf verwendet (also nicht erwartet) wird. Der Aufrufer kann dabei mit den von der Methode zurückgegebenen Tasks arbeiten oder sie zur Vermeidung von unnötigen Unterbrechungspunkten selbst wieder an den eigenen Aufrufer zurückgeben.

[ header = Asynchrone Programmierung in Windows-8-Anwendungen – Teil 2 ]

Namenskonventionen

Microsoft gibt die Konvention vor, dass alle asynchronen oder erwartbaren Methoden einen Namen haben, der auf Async endet, z. B. GetStringAsync aus WinRT-Klasse HttpClient. In manchen Fällen endet der Name auf TaskAsync, wie das äquivalente DownloadStringTaskAsync aus der .NET-4.5-Klasse WebClient. In diesem Fall gab es bereits in .NET 2.0 ein DownloadStringAsync, das nach dem EAP implementiert wurde. Im Fall von Event Handlern darf die Konvention mit dem Segen aus Redmond ignoriert werden und Methoden wie Button_Click sind weiter erlaubt. Die Beispiele im Folgenden verwenden diese Konvention ebenfalls nicht und geben allen Methoden einen im Rahmen der Erklärungen sinnvolleren Namen.

Implementierung erwartbarer Methoden

Betrachten wir nun genauer das Innere von erwartbaren Methoden. Ihr Rückgabetyp ist als Task oder Task festgelegt (Regel 2). Dabei entspricht eine erwartbaren Methode, die einen Task zurückgibt, einer nichterwartbaren Methode, die void zurückgibt. Eine erwartbare Methode, die einen Task zurückgibt, entspricht einer nichterwartbaren Methode, die ein T zurückgibt. Wir haben bereits festgestellt, dass erwartbare Methoden sowohl asynchron (wie in Fall 3) als auch synchron sein können (wie in Fall 4). Beide Arten werden unterschiedlich implementiert. Beginnen wir mit asynchronen, erwartbaren Methoden und schauen uns an, woher die Tasks kommen:

async Task Bar1() {     ... await ... }  async Task Bar2() {     ... await ...     return 42; } 

Wir sehen, dass wir nichts sehen. Die benötigten Task- und Task Instanzen werden automatisch vom Compiler erzeugt und die Programmierung erfolgt, abgesehen von den Schlüsselworten async und await, ganz wie gewohnt. Der Compiler macht dabei weit mehr, als Tasks zu erzeugen, eine vollständige Beschreibung würde den Rahmen dieses Artikels aber weit sprengen. Das Schlagwort, das in diesem Kontext immer auftaucht, ist „Zustandsautomat“, und die offizielle Dokumentation führt das genauer aus.

Weiter mit synchronen, erwartbaren Methoden. Wie bereits erwähnt, handelt es sich hier um das Ende einer verschachtelten Methodenfolge, meistens in der Form von Methoden aus dem WinRT-API. Eigene Methoden können wie in Listing 1 implementiert werden.

Listing 1
Task Bar1() {     ...     return Task.Run(() =>     {         ...     }); }  Task Bar2() {     ...     return Task.Run(() =>     {         ...         return 42;     }); }

Task.Run erzeugt einen Task, der den enthaltenen Lambda-Ausdruck unabhängig vom User Interface Thread auf einem Thread im Hintergrund ausführt. Der Task wird dabei sofort gestartet, noch bevor er an den Aufrufer zurückgegeben wird. In begründeten Fällen darf auch vor der Erzeugung und Rückgabe des Tasks Code ausgeführt werden. Dieser sollte aber möglichst wenig Zeit verbrauchen und nur der Vorbereitung des Tasks dienen. Hier können z. B. die Parameter der Methode überprüft werden oder Aufrufe erfolgen, die unbedingt im User Interface Thread stattfinden müssen.

Natürlich dürfen die Methoden auch Parameter haben, und dank der Unterstützung von Closures durch den Compiler können sie im Inneren der Lambda-Ausdrücke verwendet werden.

Andere Implementierungen, die keine Tasks und Lambdas verwenden, sondern APM- oder EAP-Implementierungen mit TAP kompatibel machen, sind ebenfalls möglich und in der WinRT weit verbreitet.

Kontrollfluss

Nach dieser umfangreichen Vorbereitung wird es Zeit, echte Beispiele zu betrachten. Für den Fall von rein synchronen, verschachtelten Methodenfolgen wissen wir bereits, wie der Kontrollfluss aussieht, und für den Fall von rein asynchronen, verschachtelten Methodenfolgen erwarten wir keine Überraschungen. Die Frage ist, was an den Übergängen von der synchronen in die asynchrone Welt passiert.

Das Beispiel CallFlowSample1 aus dem Quelltext zum Artikel beinhaltet eine einfache verschachtelte Methodenfolge und ist – auf das Wesentliche reduziert – in Listing 2 dargestellt.

Listing 2
void NormalMethod() {     Console.WriteLine("1. Entering NormalMethod and calling AsyncMethod");     AsyncMethod();     Console.WriteLine("4. Returning from NormalMethod "); }  async void AsyncMethod() {     Console.WriteLine("2. Entering AsyncMethod and calling GetTask");     var task = GetTask();     Console.WriteLine("4. Awaiting task");     await task;     Console.WriteLine("6. Returning from AsyncMethod"); }  Task GetTask() {     Console.WriteLine("3. Creating task");     return Task.Run(() =>     {         Console.WriteLine("4. Entering task");         Work.Do();         Console.WriteLine("5. Returning from task");     }); }

Der Kontrollfluss beginnt mit dem Aufruf von NormalMethod (Fall 1), danach folgen der Aufruf von AsyncMethod (Fall 2) und das Erwarten von GetTask (Fall 4). Innerhalb des Tasks erfolgen schließlich in Work.Do noch einige Berechnungen, die ein wenig Zeit brauchen. Wie die Nummerierung in dem Listing vermuten lässt, erzeugt der Quelltext (in den allermeisten Fällen) die Ausgabe in Listing 3.

Listing 3
1. Entering NormalMethod and calling AsyncMethod 2. Entering AsyncMethod and calling GetTask 3. Creating task 4. Awaiting task 4. Entering task 4. Returning from NormalMethod 5. Returning from task 6. Returning from AsyncMethod

[ header = Asynchrone Programmierung in Windows-8-Anwendungen – Teil 3 ]

Zunächst fällt auf, dass der vierte Index dreimal vergeben ist. Die Reihenfolge dieser drei Operationen kann zur Laufzeit schwanken, je nachdem, wie der Scheduler gerade arbeitet. Wichtig ist hier aber etwas ganz anderes, nämlich dass der Kontrollfluss NormalMethod schon vor GetTask und AsyncMethod verlässt und später innerhalb des Tasks fortgesetzt wird. Das entspricht so gar nicht dem Verhalten von synchronen Methodenaufrufen – aber wir haben ja bereits erwartet, dass das Erwarten einer Methode sich anders verhält als ein Aufruf. In diesem Fall kann die Konsolenanwendung, in die das Beispiel eingebettet ist, schon nach Schritt 4 beendet werden (um das einfacher auszuprobieren, vermehren Sie einfach die Arbeit im Task).

Es ist möglich, aus NormalMethod heraus mehr als eine asynchrone Methode aufzurufen, und die Tasks am Ende dieser Folgen werden dann auch echt parallel ausgeführt. Sinnvoll ist diese Vorgehensweise aber nicht, denn weder können diese Tasks untereinander oder mit NormalMethod synchronisiert noch können ihre Ergebnisse aggregiert und verwendet werden. Lediglich ein einfaches Fire-and-Forget ist hier möglich, siehe dazu das Beispiel ParallelSample.

Das Beispiel CallFlowSample2 enthält ein paar asynchrone Methoden mehr und deckt damit auch Fall 3 ab. Es wird wegen seiner Länge hier nicht abgedruckt und kann im Quelltext zum Artikel eingesehen werden. Das Beispiel ist genau genommen sogar zweimal enthalten, einmal als .NET-4.5-Konsolenanwendung und einmal als WinRT-XAML-Anwendung. Die Ausgabe beider Versionen des Beispiels ist in Abbildung 1 zu sehen.

Abb. 1: Ausgabe von CallFlowSample2

Es beantwortet die Frage, wann die Rückkehr aus NormalMethod heraus genau erfolgt: Sie erfolgt nicht schon beim ersten await, sondern erst beim Wechseln in und Warten auf den Hintergrund-Thread des am Ende erzeugten Tasks.

Die Beispiele CallFlowSample3 und CallFlowSample4 zeigen schließlich noch, wie unnötige Unterbrechungspunkte vermieden werden können (Fall 5), und dass der Kontrollfluss zwischen rein asynchronen Methoden tatsächlich derselbe ist wie zwischen rein synchronen Methoden.

Thread-Kontext

CallFlowSample2 und Abbildung 1 beantworten nicht nur die Frage nach dem Kontrollfluss bei verschachtelten Methodenfolgen, sondern auch die Frage nach dem Thread-Kontext, in dem alle beteiligten Methoden ausgeführt werden. Dabei fällt sofort auf, dass sich die Konsolenanwendung anders verhält als die XAML-Anwendung. Der Anfang des Call Stacks wird in beiden Fällen im User Interface Thread ausgeführt, und in diesem erfolgt auch die Rückkehr aus NormalMethod. Der Task wird danach in einem eigenen Thread im Hintergrund ausgeführt. Das Verhalten ändert sich zwischen beiden Versionen aber später bei der Rückkehr aus dem Task. Die Konsolenanwendung macht in dem Hintergrund-Thread des Tasks weiter, während die XAML-Anwendung wieder in den User Interface Thread wechselt. Das ist auch gut so, denn schon in Windows Forms, WPF und Silverlight können Änderungen am User Interface nur aus dem User Interface Thread heraus erfolgen. Änderungen, die zur Laufzeit aus einem anderen Thread heraus vorgenommen werden, führen zu einer Ausnahme. Hier ist das nicht anders, und die Tatsache, dass der Quelltext automatisch wieder im richtigen Thread landet, macht die Entwicklung sehr angenehm. Wir müssen lediglich in synchronen, erwartbaren Methoden darauf achten, das User Interface nicht zu verändern, aber das verbietet ja schon das Single Responsibility Principle (die Methode ist nur zum Rechnen da und nicht, um auch das User Interface zu aktualisieren).

Verantwortlich für dieses unterschiedliche Verhalten sind die WinRT-Klasse SynchronizationContext und ihre abgeleiteten Klassen. An dieser Stelle sei nur gesagt, dass dieser Kontext in Konsolenanwendungen nicht gesetzt ist – wie wir gesehen haben, erfolgt auch keine Synchronisierung – und für Windows Forms, WPF, Windows 8 XAML und ASP.NET-Anwendungen jeweils ein anderer ist.

Rückgabewerte

Dass erwartbare Methoden Werte zurückgeben können, haben wir bereits gesehen. Für die Rückgabe, z. B. eines int, muss der Rückgabetyp einer Methode als Task definiert werden. Wenn wir GetTask aus Listing 2 so verändern, dass es einen Wert zurückgibt, dann können wir diesen Wert in AsyncMethod verwenden. Siehe dazu auch das Beispiel ReturnValueSample. Die Rückkehr von AsyncMethod heraus in NormalMethod hinein (Fall 1) stellt aber eine Barriere dar (Listing 4).

Listing 4
void NormalMethod() {     AsyncMethod();     // result? }  async void AsyncMethod() {     int result = await GetTask(); }  Task GetTask()  {     return Task.Run(() =>         { Work.Do(); return 42; }); }

An der mit dem Kommentar markierten Stelle wurde der Rückgabewert potenziell noch gar nicht berechnet und kann dort daher nicht verwendet werden. Alle Änderungen am Quelltext, die dazu führen, dass er in NormalMethod verfügbar ist, machen aus ihr am Ende eine asynchrone Methode und verschieben damit die Barriere im Call Stack eine Ebene weiter nach oben zu der synchronen Methode, aus der heraus NormalMethod aufgerufen wird. Oder sie setzen voraus, dass der Task für NormalMethod sichtbar gemacht wird, sodass sie sich mit ihm synchronisieren kann.

Ausnahmebehandlung

Die Ausnahmebehandlung beim Erwarten einer Methode funktioniert, wie im Beispiel ExceptionHandlingSample gezeigt, genauso wie beim Aufrufen einer Methode (Listing 5).

Listing 5
void NormalMethod() {     AsyncMethod();     // exception? }  async void AsyncMethod {     try { await GetTask(); }     catch (Exception) { ... } }  Task GetTask()  {     return Task.Run(() =>         { Work.Do(); throw new Exception(); }); }

Wichtig hier ist wieder die Barriere bei der Rückkehr nach NormalMethod. Genauso wie in den synchronen Methoden nicht auf die Rückgabewerte von Tasks zugegriffen werden kann, können dort auch keine Ausnahmen gefangen werden, die potenziell noch gar nicht geworfen wurden. Für das Werfen von Ausnahmen in synchronen, erwartbaren Methoden muss unterschieden werden, was die Ausnahme ausdrücken soll: Fehler bei der Verwendung der Methode bzw. Fehler, die schon vor dem Starten des Task offensichtlich sind, werden vor dem Erzeugen und Starten des Task geworfen. Fehler, die erst zur Laufzeit des Tasks auftreten können, werden im Task geworfen:

Task GetTask(object parameter)  {     if (parameter == null)         throw new ArgumentNullException(“parameter”);      return Task.Run(() =>         { ... throw new OutOfMemoryException(); ... }); }
Abbruch asynchroner Operationen

Das Abbrechen asynchroner Operationen erfolgt über die WinRT-Klasse CancellationTokenSource und den von ihr erzeugten CancellationToken-Werten. Eine Instanz der Klasse wird dort erzeugt, wo auch die Entscheidung über einen Abbruch getroffen werden wird, aber vor dem Erwarten der abzubrechenden, asynchronen Operation oder Operationen. Die Instanz liefert einen Token-Wert, der an die Operationen übergeben und so lange im Call Stack weitergereicht wird wie nötig. Über die Instanz kann dann schließlich ein Abbruch angefordert werden. Um diese Anforderung zu erkennen, muss regelmäßig das Token abgefragt werden. Der eigentliche Abbruch kann dann auf zwei Arten erfolgen, die sich darin unterscheiden, welchen Status der abgebrochene Task nach dem Abbruch hat. Die Beispiele ExceptionHandlingSample1 und ExceptionHandlingSample2 zeigen die Implementierung beider Arten.

Abschluss

Ich hoffe, der Artikel hat Ihr Verständnis der asynchronen Entwicklung für Windows 8 gestärkt und hilft Ihnen bereits, Fehler in der Praxis schneller zu erkennen und zu beseitigen. Teil 2 wird das Gelernte in das XAML- und MVVM-Umfeld einordnen.

Wer sich tiefer mit dem Thema und der TPL beschäftigen oder die alte, asynchrone Welt aus .NET 1.0 und 2.0 mit der neuen, asynchronen Welt aus .NET 4.5 verbinden möchte, findet weitere Informationen auf der „Visual Studio Asynchronous Programming“-Webseite [1]. Insbesondere die Links „Whitepaper: Asynchrony in .NET“ [2], „Task-Based Asynchronous Pattern Overview“ [3] und „C# Language Specification for Asynchronous Functions“ [4] enthalten alle wichtigen Informationen.

Wer sich abschließend noch Sorgen um die Performanz von asynchronen Methodenaufrufen macht, findet weitere Informationen zu diesem Thema in „Async Performance: Understanding the Costs of Async and Await“ von Stephen Toub [5].

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -