Zusammenfassung von Kapitel 20: Asynchrone Verarbeitung und Datei-E/A
Hinweis
Dieses Buch wurde im Frühjahr 2016 veröffentlicht und seitdem nicht aktualisiert. Wenngleich ein großer Teil des Buchs weiterhin relevante Informationen liefert, sind einige Abschnitte veraltet, und einige Themen sind nicht mehr korrekt oder vollständig.
Eine grafische Benutzeroberfläche muss sequenziell auf Benutzereingabeereignisse reagieren. Dies impliziert, dass die gesamte Verarbeitung von Benutzereingabeereignissen in einem einzigen Thread erfolgen muss, der häufig als Hauptthread oder UI-Thread bezeichnet wird.
Benutzer erwarten von grafischen Benutzeroberflächen eine kurze Reaktionszeit. Dies bedeutet, dass ein Programm Benutzereingabeereignisse schnell verarbeiten muss. Wenn dies nicht möglich ist, muss die Verarbeitung auf sekundäre Verarbeitungsthreads verlagert werden.
In verschiedenen Beispielprogrammen in diesem Buch wurde die WebRequest
-Klasse verwendet. In dieser Klasse startet die BeginGetResponse
-Methode einen Arbeitsthread, der nach seinem Abschluss eine Rückruffunktion aufruft. Diese Rückruffunktion wird jedoch im Arbeitsthread ausgeführt, sodass das Programm die Device.BeginInvokeOnMainThread
-Methode aufrufen muss, um auf die Benutzerschnittstelle zuzugreifen.
Hinweis
Für Xamarin.Forms-Programme sollte HttpClient
anstelle von WebRequest
für den Zugriff auf Dateien über das Internet verwendet werden. HttpClient
unterstützt asynchrone Vorgänge.
Ein moderner Ansatz für die asynchrone Verarbeitung steht in .NET und C# zur Verfügung. Dies schließt die Klassen Task
und Task<TResult>
, weitere Typen in den Namespaces System.Threading
und System.Threading.Tasks
sowie die C# 5.0-Schlüsselwörter async
und await
ein. Darauf konzentriert sich dieses Kapitel.
Von Rückrufen zu „await“
Die Page
-Klasse selbst enthält drei asynchrone Methoden zur Anzeige von Warnfeldern:
DisplayAlert
gibt einTask
-Objekt zurück.DisplayAlert
gibt einTask<bool>
-Objekt zurück.DisplayActionSheet
gibt einTask<string>
-Objekt zurück.
Die Task
-Objekte weisen darauf hin, dass diese Methoden das aufgabenbasierte asynchrone Muster (Task-based Asynchronous Pattern, TAP) verwenden. Diese Task
-Objekte werden von der Methode schnell zurückgegeben. Die Task<T>
-Rückgabewerte stellen eine „Zusage“ (Promise) dar, dass ein Wert vom Typ TResult
zur Verfügung steht, wenn die Aufgabe abgeschlossen wurde. Der Task
-Rückgabewert weist auf eine asynchrone Aktion hin, die ausgeführt wird, aber keinen Wert zurückgibt.
In all diesen Fällen ist Task
abgeschlossen, wenn der Benutzer das Warnfeld schließt.
Eine Warnung mit Rückrufen
Das Beispiel AlertCallbacks veranschaulicht, wie Task<bool>
-Rückgabeobjekte und Device.BeginInvokeOnMainThread
-Aufrufe mithilfe von Rückrufmethoden verarbeitet werden.
Warnung mit Lambda-Ausdrücken
Das Beispiel AlertLambdas veranschaulicht, wie anonyme Lambda-Funktionen zum Verarbeiten von Task
- und Device.BeginInvokeOnMainThread
-Aufrufen verwendet werden.
Warnung mit „await“
Ein geradlinigerer Ansatz umfasst die in C# 5 eingeführten Schlüsselwörter async
und await
. Ihre Verwendung wird im Beispiel AlertAwait veranschaulicht.
Warnung mit „nothing“
Wenn die asynchrone Methode Task
anstelle von Task<TResult>
zurückgibt, muss das Programm keine dieser Techniken verwenden, wenn es nicht wissen muss, wann die asynchrone Aufgabe abgeschlossen ist. Dies wird im Beispiel NothingAlert gezeigt.
Asynchrones Speichern der Programmeinstellungen
Das Beispiel SaveProgramChanges veranschaulicht die Verwendung der SavePropertiesAsync
-Methode von Application
zum Speichern von Programmeinstellungen, wenn sich diese ohne Überschreiben der OnSleep
-Methode ändern.
Plattformunabhängiger Zeitgeber
Es ist möglich, mit Task.Delay
einen plattformunabhängigen Zeitgeber zu erstellen. Dies wird im Beispiel TaskDelayClock gezeigt.
Dateieingabe/-ausgabe
Traditionell war der .NET-Namespace System.IO
die Quelle zur Unterstützung der Datei-E/A. Einige Methoden in diesem Namensraum unterstützen asynchrone Vorgänge, die meisten anderen jedoch nicht. Der Namensraum unterstützt außerdem mehrere einfache Methodenaufrufe, die anspruchsvolle Datei-E/A-Funktionen ausführen.
Gute und schlechte Nachrichten
Alle Plattformen, die von lokalem Xamarin.Forms Anwendungsspeicher unterstützt werden – Speicher, der privat für die Anwendung ist.
Die Xamarin.iOS- und Xamarin.Android-Bibliotheken umfassen eine Version von .NET, die Xamarin explizit auf diese zwei Plattformen zugeschnitten hat. Sie enthalten Klassen von System.IO
, die Sie zum Ausführen von Datei-E/A-Vorgängen mit dem lokalen Anwendungsspeicher in diesen zwei Plattformen nutzen können.
Wenn Sie aber in einer Xamarin.Forms-PCL nach diesen System.IO
-Klassen suchen, werden Sie sie nicht finden. Das Problem ist, dass Microsoft die Datei-E/A für die Windows-Runtime-API komplett überarbeitet hat. Programme, die auf Windows 8.1, Windows Phone 8.1 und die universelle Windows-Plattform abzielen, verwenden System.IO
nicht für Datei-E/A.
Dies bedeutet, dass Sie die DependencyService
(zuerst in Kapitel 9 erläutert) verwenden müssen. Plattformspezifische API-Aufrufe zum Implementieren von Datei-E/A.
Hinweis
Portable Klassenbibliotheken wurden durch .NET Standard 2.0-Bibliotheken ersetzt, und .NET Standard 2.0 unterstützt System.IO
-Typen für alle Xamarin.Forms-Plattformen. Für die meisten Datei-E/A-Aufgaben ist es nicht länger nötig, einen DependencyService
zu verwenden. Informationen zu einem moderneren Ansatz für die Datei-E/A finden Sie unter Dateiverarbeitung in Xamarin.Forms.
Ein erster Versuch einer plattformübergreifenden Datei-E/A
Das Beispiel TextFileTryout definiert eine IFileHelper
-Schnittstelle für Datei-E/A und Implementierungen dieser Schnittstelle auf allen Plattformen. Die Windows-Runtime-Implementierungen funktionieren jedoch nicht mit den Methoden in dieser Schnittstelle, weil die Datei-E/A-Methoden der Windows-Runtime asynchron sind.
Verarbeiten von Datei-E/A-Vorgängen in der Windows-Runtime
Programme, die unter der Windows-Runtime ausgeführt werden, verwenden Klassen in den Namespaces Windows.Storage
und Windows.Storage.Streams
für Datei-E/A-Vorgänge, einschließlich des lokalen Speichers der Anwendung. Da Microsoft festgelegt hat, dass jeder Vorgang mit einer Dauer von mehr als 50 Millisekunden asynchron sein sollte, um den UI-Thread nicht zu blockieren, sind diese Datei-E/A-Methoden meist asynchron.
Der Code zur Veranschaulichung dieses neuen Ansatzes ist in einer Bibliothek enthalten, damit er von anderen Anwendungen verwendet werden kann.
Plattformspezifische Bibliotheken
Es ist vorteilhaft, wiederverwendbaren Code in Bibliotheken zu speichern. Dies ist natürlich schwieriger, wenn verschiedene Teile des wiederverwendbaren Codes für völlig unterschiedliche Betriebssysteme bestimmt sind.
Die Projektmappe Xamarin.FormsBook.Platform zeigt einen Ansatz. Diese Projektmappe enthält verschiedene Projekte:
- Xamarin.FormsBook.Platform, eine normale Xamarin.Forms-PCL
- Xamarin.FormsBook.Platform.iOS, eine iOS-Klassenbibliothek
- Xamarin.FormsBook.Platform.Android, eine Android-Klassenbibliothek
- Xamarin.FormsBook.Platform.UWP, eine UWP-Klassenbibliothek
- Xamarin.FormsBook.Platform.WinRT, ein freigegebenes Projekt für Code, der allen Windows-Plattformen gemeinsam ist
Alle Plattformprojekte (mit Ausnahme von Xamarin.FormsBook.Platform.WinRT) enthalten Verweise auf Xamarin.FormsBook.Platform. Die drei Windows-Projekte enthalten einen Verweis auf Xamarin.FormsBook.Platform.WinRT.
Alle Projekte enthalten eine statische Toolkit.Init
-Methode, um sicherzustellen, dass die Bibliothek geladen wird, wenn von einem Projekt in einer Xamarin.Forms-Anwendungsprojektmappe nicht direkt darauf verwiesen wird.
Das Projekt Xamarin.FormsBook.Platform enthält die neue IFileHelper
-Schnittstelle. Alle Methoden weisen jetzt Namen mit Async
-Suffixen auf und geben Task
-Objekte zurück.
Das Projekt Xamarin.FormsBook.Platform.WinRT enthält die FileHelper
-Klasse für die Windows-Runtime.
Das Projekt Xamarin.FormsBook.Platform.iOS enthält die FileHelper
-Klasse für iOS. Diese Methoden müssen ab sofort asynchron sein. Einige der Methoden verwenden die asynchronen Versionen der in StreamWriter
und StreamReader
definierten Methoden: WriteAsync
und ReadToEndAsync
. Andere konvertieren ein Ergebnis mithilfe der FromResult
-Methode in ein Task
-Objekt.
Das Projekt Xamarin.FormsBook.Platform.Android enthält eine ähnliche FileHelper
-Klasse für Android.
Das Projekt Xamarin.FormsBook.Platform enthält ebenfalls eine FileHelper
-Klasse, die die Verwendung des DependencyService
-Objekts vereinfacht.
Um diese Bibliotheken zu verwenden, muss eine Anwendungsprojektmappe alle Projekte in der Projektmappe Xamarin.FormsBook.Platform enthalten, und jedes der Anwendungsprojekte muss einen Verweis auf die entsprechende Bibliothek in Xamarin.FormsBook.Platform enthalten.
Die Projektmappe TextFileAsync veranschaulicht die Verwendung der Xamarin.FormsBook.Platform-Bibliotheken. Jedes der Projekte enthält einen Aufruf an Toolkit.Init
. Die Anwendung nutzt asynchrone Datei-E/A-Funktionen.
Ausführung im Hintergrund
Methoden in Bibliotheken, die Aufrufe mehrerer asynchroner Methoden ( z. B. der WriteFileAsync
Methoden ReadFileASync
in der Windows-Runtime-Klasse FileHelper
) ausführen, können mithilfe der ConfigureAwait
Methode etwas effizienter ausgeführt werden, um den Wechsel zum Benutzeroberflächenthread zu vermeiden.
Keine Blockierung des UI-Treads!
Gelegentlich ist es verlockend, die Verwendung von ContinueWith
oder await
zu vermeiden, indem man die Eigenschaft Result
auf die Methoden anwendet. Dies sollte vermieden werden, da es den UI-Thread blockieren oder sogar dazu führen kann, dass die Anwendung nicht mehr reagiert.
Eigene await-fähige Methoden
Sie können einigen Code asynchron ausführen, indem Sie ihn an eine der Task.Run
-Methoden übergeben. Sie können Task.Run
innerhalb einer asynchronen Methode aufrufen, die einen Teil der Arbeit übernimmt.
Die verschiedenen Task.Run
-Muster werden nachfolgend diskutiert.
Die grundlegende Mandelbrot-Menge
Um die Mandelbrot-Menge in Echtzeit zu zeichnen, umfasst die Xamarin.Forms.Toolkit-Bibliothek eine Complex
-Struktur, die der im Namespace System.Numerics
ähnelt.
Das MandelbrotSet-Beispiel enthält eine CalculateMandeblotAsync
-Methode in der zugehörigen CodeBehind-Datei, die die grundlegende Schwarz-Weiß-Mandelbrot-Menge berechnet und BmpMaker
zu deren Platzierung auf einer Bitmap verwendet.
Melden des Fortschritts
Um den Fortschritt einer asynchronen Methode zu melden, können Sie eine Progress<T>
-Klasse instanziieren und Ihre asynchrone Methode so definieren, dass sie ein Argument vom Typ IProgress<T>
aufweist. Dies wird im Beispiel MandelbrotProgress gezeigt.
Abbrechen des Auftrags
Sie können eine asynchrone Methode auch so schreiben, dass sie abgebrochen werden kann. Sie beginnen mit einer Klasse namens CancellationTokenSource
. Die Token
-Eigenschaft ist ein Wert vom Typ CancellationToken
. Dieser Wert wird an eine asynchrone Funktion übergeben. Ein Programm ruft die Cancel
-Methode von CancellationTokenSource
auf (im Allgemeinen als Reaktion auf eine Benutzeraktion), um die asynchrone Funktion abzubrechen.
Die asynchrone Methode kann in regelmäßigen Abständen die Eigenschaft IsCancellationRequested
von CancellationToken
überprüfen und beendet werden, wenn die Eigenschaft true
lautet, oder einfach die Methode ThrowIfCancellationRequested
aufrufen. In letzterem Fall wird die Methode mit einer OperationCancelledException
beendet.
Das MandelbrotCancellation-Beispiel veranschaulicht die Verwendung einer Funktion, die abgebrochen werden kann.
Mandelbrot (MVVM)
Das MandelbrotXF-Beispiel umfasst eine umfangreichere Benutzerschnittstelle und basiert größtenteils auf den Klassen MandelbrotModel
und MandelbrotViewModel
:
Zurück zum Web
Die in einigen Beispielen verwendete WebRequest
-Klasse nutzt ein altmodisches asynchrones Protokoll: APM (Asynchronous Programming Model). Sie können eine solche Klasse in das moderne TAP-Protokoll konvertieren, indem Sie eine der FromAsync
-Methoden in der TaskFactory
-Klasse verwenden. Dies wird im Beispiel ApmToTap veranschaulicht.