Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Muster für asynchrone MVVM-Anwendungen: Datenbindung
Asynchroner Code mit den Schlüsselwörtern „async“ und „await“ verändert die Art und Weise, wie Programme geschrieben werden, und das mit gutem Grund. Auch wenn „async“ und „await“ für Serversoftware nützlich sein können, stehen zurzeit Anwendungen mit einer Benutzeroberfläche im Mittelpunkt. Für solche Anwendungen kann mit diesen Schlüsselwörtern eine Benutzeroberfläche mit verbesserter Reaktionsfähigkeit erstellt werden. Es ist jedoch nicht sofort ersichtlich, wie „async“ und „await“ mit etablierten Mustern wie Model-View-ViewModel (MVVM) verwendet werden können. Dieser Artikel ist der erste in einer kurzen Reihe, in der Muster zur kombinierten Verwendung von „async“ und „await“ mit MVVM erläutert werden.
Mein erster Artikel zu „async“ mit dem Titel „Bewährte Verfahren bei der asynchronen Programmierung“ (msdn.microsoft.com/magazine/jj991977.aspx) bezog sich auf alle Anwendungen, die „async“/„await“ verwenden, und zwar auf Client- sowie Serveranwendungen. Diese neue Reihe baut auf den bewährten Verfahren aus diesem Artikel auf und stellt Muster vor, die speziell für clientseitige MVVM-Anwendungen gelten. Ein Muster ist jedoch einfach nur ein Muster und muss nicht unbedingt auch die beste Lösung für ein bestimmtes Szenario sein. Falls Sie eine bessere Methode kennen, würde ich mich über jeden Hinweis von Ihnen freuen!
Zu diesem Zeitpunkt werden die Schlüsselwörter „async“ und „await“ von vielen MVVM-Plattformen unterstützt: Desktop (Windows Presentation Foundation [WPF] mit Microsoft .NET Framework 4 und höher), iOS/Android (Xamarin), Windows Store (Windows 8 und höher), Windows Phone (Version 7.1 und höher), Silverlight (Version 4 und höher) sowie Portable Class Libraries (PCLs) auf einer beliebigen Kombination dieser Plattformen (wie MvvmCross). Jetzt ist es an der Zeit, „asynchrone MVVM”-Muster zu entwickeln.
Ich gehe davon aus, dass Sie sich bereits etwas mit „async“ und „await“ und relativ gut mit MVVM auskennen. Falls dies nicht der Fall ist, können Sie eine Vielzahl hilfreicher Einführungsmaterialen aus dem Internet heranziehen. Mein Blog (bit.ly/19IkogW) umfasst eine Einführung zu „async“/„await“, in der am Ende zusätzliche Ressourcen aufgeführt sind. Auch die MSDN-Dokumentation zu „async“ enthält viele nützliche Informationen (suchen Sie nach dem englischen Begriff „Task-based Asynchronous Programming“ oder nach den deutschen Entsprechungen wie „aufgabenbasierte asynchrone Programmierung“). Wenn Sie sich noch mehr über MVVM informieren möchten, empfiehlt sich praktisch alles, was Josh Smith geschrieben hat.
Eine einfache Anwendung
In diesem Artikel erstelle ich eine äußerst einfache Anwendung (siehe Abbildung 1). Beim Laden der Anwendung wird eine HTTP-Anforderung gestartet, und es wird die Anzahl zurückgegebener Bytes gezählt. Die HTTP-Anforderung wird möglicherweise erfolgreich oder mit einer Ausnahme abgeschlossen, und die Anwendung wird mithilfe der Datenbindung aktualisiert. Die Anwendung ist zu jedem Zeitpunkt vollständig reaktionsfähig.
Abbildung 1: Die Beispielanwendung
Ich möchte Ihnen nicht verschweigen, dass ich in meinen eigenen Projekten dem MVVM-Muster nicht immer 100-prozentig folge. Manchmal verwende ich ein vollständiges Domänenmodell, aber häufiger greife ich auf einen Satz von Diensten und Datentransferobjekten (im Grunde eine Datenzugriffsschicht) anstelle eines tatsächlichen Modells zurück. Außerdem gehe ich eher pragmatisch vor, was das „View“-Element betrifft. Lieber verwende ich ein paar Zeilen Codebehind anstatt etlicher Codezeilen zum Unterstützen von Klassen und XAML. Wenn ich mich also auf MVVM beziehe, halte ich mich an keine bestimmte strenge Definition des Begriffs.
Beim Einbeziehen von „async“ und „await“ in das MVVM-Muster müssen Sie als Erstes identifizieren, welche Teile Ihrer Lösung den UI-Threadingkontext benötigen. Windows-Plattformen ermöglichen den Zugriff auf Benutzeroberflächenkomponenten nur über den UI-Thread, der sie besitzt. Offensichtlich ist die Ansicht vollständig an den Benutzeroberflächenkontext gebunden. Hinsichtlich meiner Anwendungen vertrete ich auch die Auffassung, dass alles, was über Datenbindung mit der Ansicht verknüpft ist, an den Benutzeroberflächenkontext gebunden ist. In neueren Versionen von WPF wurde diese Einschränkung gelockert. Zwischen dem UI-Thread und Hintergrundthreads (zum Beispiel „BindingOperations.EnableCollectionSynchronization“) ist jetzt in bestimmtem Umfang eine Datenfreigabe möglich. Jedoch ist die Unterstützung von threadübergreifender Datenbindung nicht auf jeder MVVM-Plattform garantiert (WPF, iOS/Android/Windows Phone, Windows Store). Folglich behandle ich in meinen eigenen Projekten jedes an die Benutzeroberfläche datengebundene Element so, als ob es UI-Threadaffinität hätte.
Meine „ViewModels“ werden also immer so behandelt, als ob sie an den Benutzeroberflächenkontext gebunden wären. In meinen Anwendungen bezieht sich „ViewModel“ eher auf „View“ als auf „Model“, und die ViewModel-Schicht ist in erster Linie eine API für die gesamte Anwendung. Die „View“ stellt ihrem Sinn gemäß nur die Shell der Benutzeroberflächenelemente dar, in der sich die eigentliche Anwendung befindet. Die ViewModel-Schicht ist vom Konzept her eine testfähige Benutzeroberfläche, vollständig mit UI-Threadaffinität. Wenn Ihr „Model“ ein echtes Domänenmodell (und keine Datenzugriffsschicht) ist und zwischen „Model“ und „ViewModel“ Datenbindung besteht, dann verfügt auch das „Model“ selbst über UI-Threadaffinität. Ermitteln Sie zuerst, welche Schichten eine Affinität zur Benutzeroberfläche besitzen. Dann sollten Sie in der Lage sein, eine geistige Linie zu ziehen zwischen dem „benutzeroberflächenaffinen Code“ („View“ und „ViewModel“ und möglicherweise „Model“) und dem „benutzeroberflächenunabhängigen Code” (möglicherweise „Model“ und definitiv alle anderen Schichten wie Dienste und Datenzugriff).
Darüber hinaus sollte der Code außerhalb der View-Schicht (also der „ViewModel“- und „Model“-Schichten, -Dienste usw.) von keinem Typ abhängen, der an eine bestimmte Benutzeroberflächenplattform gebunden ist. Ungeeignet ist eine direkte Verwendung von „Dispatcher“ (WPF/Xamarin/Windows Phone/Silverlight), „CoreDispatcher“ (Windows Store) oder „ISynchronizeInvoke“ (Windows Forms). („SynchronizationContext“ ist nur unwesentlich besser geeignet). Beispielsweise befindet sich im Internet viel Code, der asynchrone Aufgaben ausführt und dann mithilfe von „Dispatcher“ die Benutzeroberfläche aktualisiert. Besser übertragbar und weniger mühsam wäre es, mithilfe von „await“ asynchrone Aufgaben auszuführen und anschließend ohne „Dispatcher“ die Benutzeroberfläche zu aktualisieren.
„ViewModels“ stellen die interessanteste Schicht dar, weil sie eine Affinität zur Benutzeroberfläche besitzen, aber nicht von einem bestimmten Benutzeroberflächenkontext abhängen. In dieser Reihe werden „async“ und „MVVM“ so miteinander kombiniert, dass bestimmte Benutzeroberflächentypen vermieden, aber bewährte Verfahren für „async“ befolgt werden. Der vorliegende erste Artikel konzentriert sich auf die asynchrone Datenbindung.
Asynchrone datengebundene Eigenschaften
Der Begriff „asynchrone Eigenschaft“ ist eigentlich ein Oxymoron. Eigenschaftengetter sollten sofort ausgeführt werden und aktuelle Werte abrufen, anstatt Hintergrundoperationen zu starten. Dies ist sicherlich einer der Gründe dafür, dass das Schlüsselwort „async“ für den Eigenschaftengetter nicht verwendet werden kann. Wenn Ihr Entwurf eine asynchrone Eigenschaft erfordert, sollten Sie zuerst Alternativen abwägen. Sie sollten sich insbesondere fragen: Sollte die Eigenschaft eine Methode (oder ein Befehl) sein? Wenn der Eigenschaftengetter bei jedem Zugriff eine neue asynchrone Operation starten muss, handelt es sich gar nicht um eine Eigenschaft. Asynchrone Methoden sind relativ unkompliziert, und die entsprechenden Befehle behandle ich in einem anderen Artikel.
In diesem Artikel möchte ich eine asynchrone datengebundene Eigenschaft entwickeln, also eine datengebundene Eigenschaft, die ich mit den Ergebnissen einer async-Operation aktualisiere. Es kommt häufiger vor, dass ein „ViewModel“ aus einer externen Quelle Daten abrufen muss.
Wie ich oben hinsichtlich meiner Beispielanwendung erklärt habe, möchte ich einen Dienst definieren, der Bytes auf einer Webseite zählt. Um die Reaktionsfähigkeit von „async“/„await“ darzustellen, wird auch eine Verzögerung um ein paar Sekunden stattfinden. Weitere realistische asynchrone Dienste werde ich in einem der nächsten Artikel besprechen. In diesem Artikel stellt der „Service“ nur die einzelne Methode in Abbildung 2 dar.
Abbildung 2: „MyStaticService.cs“
using System;
using System.Net.Http;
using System.Threading.Tasks;
public static class MyStaticService
{
public static async Task<int> CountBytesInUrlAsync(string url)
{
// Artificial delay to show responsiveness.
await Task.Delay(TimeSpan.FromSeconds(3)).ConfigureAwait(false);
// Download the actual data and count it.
using (var client = new HttpClient())
{
var data = await client.GetByteArrayAsync(url).ConfigureAwait(false);
return data.Length;
}
}
}
Beachten Sie, dass dies als Dienst betrachtet wird, folglich besteht eine Unabhängigkeit von der Benutzeroberfläche. Da der Dienst von der Benutzeroberfläche unabhängig ist, wird bei jedem „await“ „ConfigureAwait(false)“ verwendet (wie in meinem anderen Artikel „Bewährte Verfahren bei der asynchronen Programmierung“ besprochen).
Fügen wir ein einfaches View- und ViewModel-Element hinzu, das beim Start eine HTTP-Anforderung startet. Im Beispielcode werden WPF-Fenster mit View-Elementen verwendet, die ihre eigenen „ViewModels“ während der Erstellung erstellen. Dies geschieht nur der Einfachheit halber. Die in dieser Reihe besprochenen async-Prinzipien und -Muster gelten für alle MVVM-Plattformen, Frameworks und Bibliotheken. Im Rahmen dieses Artikels besteht das View-Element aus einem einzelnen Hauptfenster mit einer einzelnen Bezeichnung. Die XAML für das zentrale View-Element wird nur an den UrlByteCount-Member gebunden:
<Window x:Class="MainWindow"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Label Content="{Binding UrlByteCount}"/>
</Grid>
</Window>
Der Codebehind für das Hauptfenster erstellt „ViewModel“:
public partial class MainWindow
{
public MainWindow()
{
DataContext = new BadMainViewModelA();
InitializeComponent();
}
}
Häufige Fehler
Ihnen ist sicherlich aufgefallen, dass der ViewModel-Typ „BadMainViewModelA“ heißt. Das liegt daran, dass ich zuerst auf ein paar häufige Fehler im Zusammenhang mit „ViewModels“ hinweisen möchte. Einer besteht darin, synchron die Operation zu blockieren, etwa so:
public class BadMainViewModelA
{
public BadMainViewModelA()
{
// BAD CODE!!!
UrlByteCount =
MyStaticService.CountBytesInUrlAsync("http://www.example.com").Result;
}
public int UrlByteCount { get; private set; }
}
Dies ist ein Verstoß gegen die async-Richtlinie „durchgehend asynchron“. Gelegentlich greifen Entwickler jedoch darauf zurück, wenn sie denken, dass sie keine Alternativen haben. Wenn Sie den Code ausführen, funktioniert er bis zu einem gewissen Grad. Code, der „Task.Wait“ oder „Task<T>.Result“ anstelle von „await“ verwendet, blockiert die Operation synchron.
Das synchrone Blockieren ist mit ein paar Problemen verbunden. Das offensichtlichste besteht darin, dass der Code jetzt eine asynchrone Operation verwendet und blockiert. Dadurch gehen die Vorteile der Asynchronität verloren. Wenn Sie den gegenwärtigen Code ausführen, wird die Anwendung ein paar Sekunden lang angehalten. Anschließend wird das vollständige Benutzeroberflächenfenster mit den bereits ausgefüllten Ergebnissen angezeigt. Allerdings reagiert die Anwendung nicht, was für viele moderne Anwendungen inakzeptabel ist. Der Beispielcode enthält eine absichtliche Verzögerung, um die fehlende Reaktion hervorzuheben. In einer echten Anwendung wird dieses Problem während der Entwicklung möglicherweise nicht bemerkt und zeigt sich nur in „ungewöhnlichen“ Clientszenarien (wie beim Verlust der Netzwerkkonnektivität).
Ein weiteres Problem des synchronen Blockierens ist subtiler: Der Code ist schneller fehleranfällig. In meinem Beispieldienst wird „ConfigureAwait(false)“ ordnungsgemäß verwendet, wie es ein Dienst tun sollte. Dies wird jedoch leicht vergessen, insbesondere wenn Sie (oder Ihre Kollegen) „async“ nicht regelmäßig verwenden. Überlegen Sie sich, was im Laufe der Zeit passieren könnte, während der Dienstcode verwaltet wird. Möglicherweise vergisst ein Verwaltungsentwickler ein „ConfigureAwait“, und dann würde sich das Blockieren des UI-Threads zu einem Deadlock des UI-Threads entwickeln. (Dies wird in meinem vorherigen Artikel zu den bewährten Verfahren für „async“ für genauer besprochen.)
Gut, Sie sollten also das Prinzip „durchgehend asynchron“ anwenden. Viele Entwickler gehen jedoch nach der zweiten falschen Vorgehensweise vor (siehe Abbildung 3).
Abbildung 3: „BadMainViewModelB.cs“
using System.ComponentModel;
using System.Runtime.CompilerServices;
public sealed class BadMainViewModelB : INotifyPropertyChanged
{
public BadMainViewModelB()
{
Initialize();
}
// BAD CODE!!!
private async void Initialize()
{
UrlByteCount = await MyStaticService.CountBytesInUrlAsync(
"http://www.example.com");
}
private int _urlByteCount;
public int UrlByteCount
{
get { return _urlByteCount; }
private set { _urlByteCount = value; OnPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Auch diesen Code können Sie erfolgreich ausführen. Die Benutzeroberfläche wird mit „0“ in der Bezeichnung sofort ein paar Sekunden lang angezeigt, bevor sie mit dem korrekten Wert aktualisiert wird. Sie reagiert, und alles scheint in Ordnung zu sein. In diesem Fall liegt das Problem jedoch bei der Fehlerbehandlung. Bei der Methode „async void“ verursachen standardmäßig alle Fehler, die von der asynchronen Operation ausgelöst werden, einen Anwendungsabsturz. Auch diese Situation kann leicht während der Entwicklung übersehen werden und wird nur unter „seltsamen“ Bedingungen auf Clientgeräten angezeigt. Auch das Ändern des Codes in Abbildung 3 von „async void“ in „async Task“ verbessert die Anwendung kaum. Alle Fehler würden still ignoriert werden und den Benutzer darüber grübeln lassen, was passiert ist. Beide Fehlerbehandlungen sind nicht angemessen. Auch wenn Lösungen möglich wären, indem Ausnahmen von der asynchronen Operation abgefangen und andere datengebundene Eigenschaften aktualisiert werden, würde dadurch viel komplizierter Code entstehen.
Eine bessere Herangehensweise
Idealerweise hätte ich einen Typ wie „Task<T>“ mit Eigenschaften zum Abrufen von Ergebnissen zu Fehlern. Leider ist „Task<T>“ aus zwei Gründen nicht für die Datenbindung geeignet: „INotifyPropertyChanged“ kann nicht implementiert werden, und seine Result-Eigenschaft wird blockiert. Sie können jedoch eine Art „Task-Watcher“ definieren, wie den Typ in Abbildung 4.
Abbildung 4: „NotifyTaskCompletion.cs“
using System;
using System.ComponentModel;
using System.Threading.Tasks;
public sealed class NotifyTaskCompletion<TResult> : INotifyPropertyChanged
{
public NotifyTaskCompletion(Task<TResult> task)
{
Task = task;
if (!task.IsCompleted)
{
var _ = WatchTaskAsync(task);
}
}
private async Task WatchTaskAsync(Task task)
{
try
{
await task;
}
catch
{
}
var propertyChanged = PropertyChanged;
if (propertyChanged == null)
return;
propertyChanged(this, new PropertyChangedEventArgs("Status"));
propertyChanged(this, new PropertyChangedEventArgs("IsCompleted"));
propertyChanged(this, new PropertyChangedEventArgs("IsNotCompleted"));
if (task.IsCanceled)
{
propertyChanged(this, new PropertyChangedEventArgs("IsCanceled"));
}
else if (task.IsFaulted)
{
propertyChanged(this, new PropertyChangedEventArgs("IsFaulted"));
propertyChanged(this, new PropertyChangedEventArgs("Exception"));
propertyChanged(this,
new PropertyChangedEventArgs("InnerException"));
propertyChanged(this, new PropertyChangedEventArgs("ErrorMessage"));
}
else
{
propertyChanged(this,
new PropertyChangedEventArgs("IsSuccessfullyCompleted"));
propertyChanged(this, new PropertyChangedEventArgs("Result"));
}
}
public Task<TResult> Task { get; private set; }
public TResult Result { get { return (Task.Status == TaskStatus.RanToCompletion) ?
Task.Result : default(TResult); } }
public TaskStatus Status { get { return Task.Status; } }
public bool IsCompleted { get { return Task.IsCompleted; } }
public bool IsNotCompleted { get { return !Task.IsCompleted; } }
public bool IsSuccessfullyCompleted { get { return Task.Status ==
TaskStatus.RanToCompletion; } }
public bool IsCanceled { get { return Task.IsCanceled; } }
public bool IsFaulted { get { return Task.IsFaulted; } }
public AggregateException Exception { get { return Task.Exception; } }
public Exception InnerException { get { return (Exception == null) ?
null : Exception.InnerException; } }
public string ErrorMessage { get { return (InnerException == null) ?
null : InnerException.Message; } }
public event PropertyChangedEventHandler PropertyChanged;
}
Nun möchte ich die zentrale Methode „NotifyTaskCompletion<T>.WatchTaskAsync“ besprechen. Diese Methode wartet darauf, dass der Task, der die asynchrone Operation darstellt, (asynchron) abgeschlossen wird. Beachten Sie, dass „await“ nicht „ConfigureAwait(false)“ verwendet. Ich möchte zum Benutzeroberflächenkontext zurückkehren, bevor die PropertyChanged-Benachrichtigungen ausgegeben werden. Diese Methode verstößt hier gegen eine übliche Codierungsrichtlinie: Es ist eine leere allgemeine catch-Klausel vorhanden. In diesem Fall ist dies jedoch genau das, was ich möchte. Ich möchte keine Ausnahmen direkt zurück auf die zentrale Benutzeroberflächenschleife übertragen, sondern Ausnahmen so erfassen und Eigenschaften so festlegen, dass die Fehler über die Datenbindung behandelt werden. Beim Abschluss der Aufgabe löst der Typ für alle entsprechenden Eigenschaften PropertyChanged-Benachrichtigungen aus.
Ein aktualisiertes „ViewModel“ mit „NotifyTaskCompletion<T>“ würde folgendermaßen aussehen:
public class MainViewModel
{
public MainViewModel()
{
UrlByteCount = new NotifyTaskCompletion<int>(
MyStaticService.CountBytesInUrlAsync("http://www.example.com"));
}
public NotifyTaskCompletion<int> UrlByteCount { get; private set; }
}
Dieses „ViewModel“ startet die Operation sofort und erstellt dann für die resultierende Aufgabe einen datengebundenen „Watcher“. Der datengebundene Code für „View“ muss aktualisiert werden, um etwa folgendermaßen eine explizite Bindung an das Ergebnis der Operation durchzuführen:
<Window x:Class="MainWindow"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Label Content="{Binding UrlByteCount.Result}"/>
</Grid>
</Window>
Sie sehen, dass der „Label Content“ an „NotifyTaskCompletion<T>.Result“ und nicht „Task<T>.Result“ datengebunden ist. „NotifyTaskCompletion<T>.Result“ ist für die Datenbindung geeignet: Es wird nicht blockiert und benachrichtigt die Bindung über die Fertigstellung der Aufgabe. Wenn Sie den Code jetzt ausführen, verhält er sich wie im vorherigen Beispiel: Die Benutzeroberfläche ist reaktionsfähig, wird sofort geladen (dabei werden die Standardwerte von „0“ angezeigt) und innerhalb weniger Sekunden mit den tatsächlichen Ergebnissen aktualisiert.
Von Vorteil ist es zudem, dass „NotifyTaskCompletion<T>“ auch viele weitere Eigenschaften besitzt. Mithilfe der Datenbindung können Sie folglich Auslastungsindikatoren oder Fehlerdetails anzeigen. Es ist nicht schwer, mit einigen dieser praktischen Eigenschaften einen Auslastungsindikator und Fehlerdetails vollständig in „View“ zu erstellen (Beispiel: der aktualisierte Datenbindungscode in Abbildung 5).
Abbildung 5: „MainWindow.xaml“
<Window x:Class="MainWindow"
xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
<Grid>
<!-- Busy indicator -->
<Label Content="Loading..." Visibility="{Binding UrlByteCount.IsNotCompleted,
Converter={StaticResource BooleanToVisibilityConverter}}"/>
<!-- Results -->
<Label Content="{Binding UrlByteCount.Result}" Visibility="{Binding
UrlByteCount.IsSuccessfullyCompleted,
Converter={StaticResource BooleanToVisibilityConverter}}"/>
<!-- Error details -->
<Label Content="{Binding UrlByteCount.ErrorMessage}" Background="Red"
Visibility="{Binding UrlByteCount.IsFaulted,
Converter={StaticResource BooleanToVisibilityConverter}}"/>
</Grid>
</Window>
Mit dieser letzten Aktualisierung, die nur „View“ ändert, wird ein paar Sekunden lang „Loading…“ angezeigt (die Anwendung bleibt dabei reaktionsfähig). Anschließend werden entweder die Ergebnisse der Operation oder eine auf einem roten Hintergrund angezeigte Fehlermeldung aktualisiert.
„NotifyTaskCompletion<T>“ behandelt einen Verwendungsfall: Sie möchten für die Ergebnisse einer asynchronen Operation eine Datenbindung durchführen. Dies ist ein häufiges Szenario beim Suchen oder Laden von Daten während des Starts. Es ist jedoch nicht besonders hilfreich, wenn Sie tatsächlich einen asynchronen Befehl besitzen, zum Beispiel „save the current record“. (In meinem nächsten Artikel werde ich asynchrone Befehle besprechen).
Auf den ersten Blick scheint das Erstellen einer asynchronen Benutzeroberfläche mühsamer zu sein, was gewissermaßen auch stimmt. Die richtige Verwendung der Schlüsselwörter „async“ und „await“erfordert dringend, dass Sie ein besseres Benutzererlebnis entwerfen. Wenn Sie zu einer asynchronen Benutzeroberfläche wechseln, können Sie die Benutzeroberfläche nicht mehr blockieren, während eine asynchrone Operation ausgeführt wird. Sie müssen überlegen, wie die Benutzeroberfläche während des Ladevorgangs aussehen soll, und einen entsprechenden Entwurf erstellen. Dies bedeutet einen höheren Aufwand. Allerdings sollte dieser zusätzliche Aufwand für die meisten modernen Anwendungen durchgeführt werden. Warum unterstützen neuere Plattformen wie Windows Store nur asynchrone APIs? Damit Entwickler ein noch besseres reaktionsfähiges Benutzererlebnis entwerfen.
Zusammenfassung
Wenn eine Codebasis von synchron in asynchron geändert wird, ändern sich normalerweise die Dienst- oder Datenzugriffskomponenten zuerst, und „async“ entwickelt sich von dort in Richtung Benutzeroberfläche. Die Umwandlung einer Methode von synchron zu asynchron wird nach ein paar Malen ganz einfach. Ich erwarte (und hoffe), dass diese Umwandlung in zukünftigen Tools automatisiert wird. Wenn jedoch „async“ auf die Benutzeroberfläche trifft, sind umfassende Änderungen erforderlich.
Wird die Benutzeroberfläche asynchron, müssen Sie sich darauf einstellen, auf nicht reaktionsfähige Anwendungen zu reagieren, indem Sie den Benutzeroberflächenentwurf verbessern. Am Ende verfügen Sie über eine modernere Anwendung mit verbesserter Reaktionsfähigkeit. „Schnell und fließend“, wenn Sie es möchten.
In diesem Artikel habe ich einen einfachen Typ vorgestellt, der als „Task<T>“ für Datenbindung zusammengefasst werden kann. Das nächste Mal werde ich asynchrone Befehle erläutern und ein Konzept untersuchen, das im Grunde genommen ein „ICommand für async“ ist. Im letzten Artikel der Reihe werde ich zusammenfassend asynchrone Dienste behandeln. Denken Sie daran, dass die Community weiterhin diese Muster entwickelt. Sie können Sie gerne an Ihre bestimmten Anforderungen anpassen.
Stephen Cleary lebt als Ehemann, Vater und Entwickler in den USA im Norden von Michigan. Seit 16 Jahren beschäftigt er sich mit Multithreading und asynchroner Programmierung und nutzt die async-Unterstützung in Microsoft .NET Framework seit der ersten CTP-Version. Seine Homepage und seinen Blog finden Sie unter stephencleary.com.
Unser Dank gilt den folgenden technischen Experten von Microsoft für die Durchsicht dieses Artikels: James McCaffrey und Stephen Toub