Schnelle und flüssige Apps mit Asynchronität in der Windows-Runtime

Entwicklerblog für Windows 8-Apps

Ein Einblick in die Entwicklung von Apps im Metro-Stil – präsentiert vom Windows 8-Entwicklerteam

Schnelle und flüssige Apps mit Asynchronität in der Windows-Runtime

  • Comments 0

Man könnte sagen, das menschliche Verhalten sei von Natur „asynchron“– und dies beeinflusst unmittelbar, welches Reaktionsverhalten wir von Apps erwarten. Asynchronität nimmt in Windows-Runtime (WinRT) beim Entwickeln von schnellen und flüssigen Apps im Metro-Stil eine besondere Stellung ein. Wenn Sie eine App im Metro-Stil erstellen, werden Sie früher oder später auch asynchronen Code schreiben müssen. In diesem Blogbeitrag sprechen wir über die herausragende Bedeutung des asynchronen Programmierens in WinRT, wir erläutern die Grundlagen zur Verwendung in Apps und geben auch einige Hintergrundinformationen zur Funktionsweise.

Schnelle und flüssige Apps müssen rasch reagieren

Wie oft haben Sie schon eine Windows-App verwendet, die plötzlich nicht mehr reagiert. Alles, was Sie noch sehen, ist eine abgeblendete Ansicht der App und die Ladeanzeige? Selbstverständlich passiert das immer in den unpassendsten Momenten. Schlimmer noch, es könnte eine Menge harter Arbeit umsonst gewesen sein.

Benutzer erwarten, dass Apps auf sämtliche Interaktionen ansprechen. In ihrer bevorzugten Nachrichten-App möchten Benutzer Feeds hinzufügen können, Artikel lesen sowie speichern usw. All das sollte auch möglich sein, während die App die neuesten Artikel aus dem Internet abruft.

Besonders wichtig ist dies, wenn ein Benutzer eine App per Fingereingabe bedient. Benutzer merken sofort, wenn die App nicht „am Finger klebt“. Selbst kleinere Leistungseinbußen können die Verwendung beeinträchtigen, denn so erscheint die App weder schnell noch flüssig.

Schnell und flüssig erscheint eine App nur, wenn sie konsequent auf Benutzereingaben anspricht. Warum also reagieren Apps manchmal nicht mehr? Ein wichtiger Grund hierfür ist, dass die App synchron ist. Die App wartet, bis ein anderer Vorgang abgeschlossen wird (z. B. das Herunterladen von Daten aus dem Internet), und kann daher nicht auf Benutzereingaben reagieren.

Viele moderne Apps stellen eine Verbindung mit den Websites sozialer Netzwerke her, speichern Daten in der Cloud, arbeiten mit Dateien auf der Festplatte, kommunizieren mit anderen Gadgets sowie Geräten usw. Bei einigen dieser Quellen treten unvorhersehbare Latenzen auf, die das Erstellen schneller und flüssiger Apps zu einer Herausforderung machen. Sind Apps nicht entsprechend programmiert, warten die Apps mitunter auf Daten aus externen Quellen, statt auf die Eingaben eines Benutzers zu reagieren.

Die Beseitigung dieser Abhängigkeiten war eines der Hauptziele beim Entwurf von APIs für Windows-Runtime (WinRT). Besonders wichtig war hierbei die Bereitstellung einer leistungsfähigen API-Oberfläche, die quasi von selbst schnelle und flüssige Apps ermöglicht.

Um dies zu erreichen, haben wir viele potenziell E/A-gebundene APIs in Windows-Runtime asynchron umgesetzt. Werden APIs als synchron erstellt, ist die Wahrscheinlichkeit am höchsten, dass sie die Leistung merklich beeinträchtigen (d. h. möglicherweise über 50 Millisekunden für die Ausführung benötigen). Dank diesem asynchronen Ansatz für APIs können Sie automatisch schnellen und flüssigen Code schreiben. Außerdem hebt der Ansatz die Bedeutung des Reaktionsverhaltens von Apps bei der Entwicklung von Apps im Metro-Stil hervor.

Wer mit asynchronen Mustern nicht vertraut ist, kann sich die Asynchronität in WinRT wie eine Rückrufnummer beim Telefonieren vorstellen. Sie geben Ihrem Gesprächspartner Ihre Rückrufnummer, legen auf und setzen Ihre bisherige Tätigkeit fort. Ihr Gesprächspartner ruft Sie anschließend unter Ihrer Nummer zurück, sobald er Zeit hat, mit Ihnen zu sprechen. Im Prinzip funktioniert Asynchronität in WinRT genau so.

Um die Asychronität von WinRT genauer zu erläutern, zeigen wir zunächst, wie Sie asynchrone APIs unkompliziert einsetzen können. Anschließend werfen wir einen genaueren Blick auf die asynchronen Primitive, die mit WinRT für die neuen Sprachfunktionen eingeführt werden, und erklären deren Funktion.

Verwendung: Neue Verbesserungen für Entwickler bezüglich Asynchronität

In der Vergangenheit war das Schreiben von asynchronem Code kompliziert. Im Zuge der Entwicklung von Tools für Windows 8-Apps im Metro-Stil haben unsere Teams in den neusten Versionen bedeutende Fortschritte bei der Lösung dieses Problems gemacht. Im .NET Framework 4.0 haben wir die Task Parallel Library eingeführt. Anschließend wurde das „Await“-Schlüsselwort in C# und Visual Basic eingeführt. In C++ kamen Technologien wie die Parallel Patterns Library (PPL) hinzu. Und JavaScript kann mit großartigen Tools wie Promises aufwarten.

Jede dieser Technologien bietet eine einfache Möglichkeit zum Schreiben von asynchronem Code. Wir haben für alle diese Technologien ein gemeinsames asynchrones Standardmuster eingeführt, das ermöglicht, diese Technologien beim Erstellen von Apps im Metro-Stil in jeder Programmiersprache zu verwenden.

Im Fall der bevorzugten Nachrichten-App verwendet der Code, der während des Herunterladens von Artikeln ansprechbar bleibt, einfach die Windows.Web.Syndication.SyndicationClient-API. Auf den Aufruf von RetrieveFeedAsync erfolgt unmittelbar eine Rückgabe. Dies ist wichtig, denn schließlich kann es einige Zeit dauern, bis das Ergebnis aus dem Internet abgerufen wird. In der Zwischenzeit reagiert die Benutzeroberfläche weiterhin auf Eingaben des Benutzers.

Der gelb markierte Code wird ausgeführt, nachdem der Aufruf von RetrieveFeedAsync abgeschlossen wurde. Sie müssen sich nicht darum kümmern, dass die App anschließend auch dort weitermacht, wo sie aufgehört hat. Und der Code ist genauso unkompliziert geschrieben wie ein synchroner Code.

JavaScript-Code zum asynchronen Abrufen eines RSS-Feeds:
var title;
var feedUri = new Windows.Foundation.Uri("http://www.devhawk.com/rss.xml");
var client = new Windows.Web.Syndication.SyndicationClient();

client.retrieveFeedAsync(feedUri).done(function(feed) {
title = feed.title.text;
});
 
C#-Code zum asynchronen Abrufen eines RSS-Feeds:
var feedUri = new Uri("http://www.devhawk.com/rss.xml");
var client = new Windows.Web.Syndication.SyndicationClient();
var feed = await client.RetrieveFeedAsync(feedUri);
var title = feed.Title.Text;

VB-Code zum asynchronen Abrufen eines RSS-Feeds:
Dim feedUri = New Uri("http://www.devhawk.com/rss.xml");
Dim client = New Windows.Web.Syndication.SyndicationClient();
Dim feed = Await client.RetrieveFeedAsync(feedUri);
Dim title = feed.Title.Text;
 

C++-Code zum asynchronen Abrufen eines RSS-Feeds:
auto feedUri = ref new Windows::Foundation::Uri("http://www.devhawk.com/rss.xml");
auto client = ref new Windows::Web::Syndication::SyndicationClient();

task<SyndicationFeed^> retrieveTask(client->RetrieveFeedAsync(feedUri));
retrieveTask.then([this] (SyndicationFeed^ feed) {
this->title = feed->Title->Text;
});

 

Threads, Kontextwechsel, Verteiler usw. spielen für Sie keine Rolle mehr. Die neuen Verbesserungen für Entwickler zum Schreiben von asynchronem Code übernehmen das für Sie. Code, der auf einen asynchronen API-Aufruf folgt, wird automatisch im selben Kontext wie der ursprüngliche Aufruf zurückgegeben. Das bedeutet, dass Sie die Benutzeroberfläche mit den Ergebnissen aktualisieren können, ohne sich um die Rückkehr zum Benutzeroberflächen-Thread kümmern zu müssen.

Verwendung: Fehlerbehandlung

Selbstverständlich können API-Aufrufe fehlschlagen (z. B. durch den Verlust der Netzwerkverbindung beim Abrufen von RSS-Feeds). Echte Stabilität entsteht nur durch Fehlertoleranz. Die Verwendung der Asynchronitätsfunktionen in den Sprachen vereinfacht die Fehlerbehandlung. Die entsprechenden Mechanismen variieren je nach Sprache.

In JavaScript sollten Sie Ihre Promise-Ketten stets mit done beenden. So wird sichergestellt, dass Ausnahmen in der Promise-Kette für den Entwickler sichtbar sind und entweder im Fehlerhandler aufgeführt oder ausgelöst werden. Done hat dieselbe Signatur wie then. Übergeben Sie einfach ein Fehler-Delegat in done():, um einen Fehler zu behandeln.

 

var title;
var feedUri = new Windows.Foundation.Uri("http://www.devhawk.com/rss.xml");
var client = new Windows.Web.Syndication.SyndicationClient();

client.retrieveFeedAsync(feedUri).done(function(feed) {
title = feed.title.text;
}, function(error) {
console.log('an error occurred: ');
console.dir(error);
}
);

Um die Ausnahme in C# oder Visual Basic zu behandeln, setzen Sie genau wie bei synchronem Code einen Try/Catch-Block ein:

 

var title;
var feedUri = new Uri("http://www.devhawk.com/rss.xml");
var client = new Windows.Web.Syndication.SyndicationClient();

try
{

var feed = await client.RetrieveFeedAsync(feedUri);
title = feed.Title.Text;
}
catch (Exception ex)
{
// An exception occurred from the async operation
}

In C++ ist die üblichste Methode zur Behandlung asynchroner Fehler die Verwendung einer weiteren aufgabenbasierten Fortsetzung, die die Ausnahmen auslöst. Im Themenbereich Asynchrones Programmieren in C++ im Entwicklungscenter finden Sie weitere Methoden zur Fehlerbehandlung in C++):

auto feedUri = ref new Windows::Foundation::Uri("http://www.devhawk.com/rss.xml");
auto client = ref new Windows::Web::Syndication::SyndicationClient();

task<SyndicationFeed^> retrieveTask(client->RetrieveFeedAsync(feedUri));
retrieveTask.then([this] (SyndicationFeed^ feed) {
this->title = feed->Title->Text;
}).then([] (task<void> t) {
try
{
t.get();
// .get() didn't throw, so we succeeded
}
catch (Exception^ ex)
{
// An error occurred
}
})
;

Ausführliche Informationen zur Verwendung dieser asynchronen Verbesserungen (inklusive weiterer Szenarien wie Unterstützung von Abbrechen oder Fortschritt) finden Sie unter:

Funktionsweise: Asynchrone Primitive in WinRT

Asynchronität ist ein mächtiger Verbündeter, jedoch scheint es mitunter, als wäre Magie im Spiel. Sehen wir uns daher einmal genauer an, wie Asynchronität in WinRT funktioniert. Im ersten Schritt erkunden wir die Primitive, auf denen das Modell basiert. In der Regel werden Sie diese Primitive überhaupt nicht verwenden müssen (falls Sie doch ein Szenario finden, für das es nötig wäre, würden wir uns über Ihr Feedback freuen).

Wir beginnen mit dem C#-Code und betrachten den tatsächlichen Rückgabetyp von RetrieveFeedAsync (hier in C# IntelliSense dargestellt):

 IntelliSense angezeigt für die „RetrieveFeedAsync“-Methode
 Abbildung 1. IntelliSense angezeigt für die „RetrieveFeedAsync“-Methode

RetrieveFeedAsync gibt eine IAsyncOperationWithProgress-Schnittstelle zurück. IAsyncOperationWithProgress ist eine von 5 Schnittstellen, die den Kern der asynchronen Programmieroberfläche von WinRT definieren: IAsyncInfo, IAsyncAction, IAsyncActionWithProgress, IAsyncOperation und IAsyncOperationWithProgress.

Die entscheidende Schnittstelle, auf der das asynchrone WinRT-Modell basiert, ist IAsyncInfo. Diese entscheidende Schnittstelle definiert die Eigenschaften eines asynchronen Vorgangs wie den aktuellen Status, die Möglichkeit, die Operation abzubrechen, den Fehler eines fehlgeschlagenen Vorgangs usw.

Wie bereits erwähnt, können asynchrone Vorgänge Ergebnisse zurückgeben. Asynchrone Vorgänge in WinRT können auch den Fortschritt anzeigen, während sie ausgeführt werden. Die anderen 4 asynchronen Schnittstellen, von denen bereits die Rede war (IAsyncAction, IAsyncActionWithProgress, IAsyncOperation und IAsyncOperationWithProgress), definieren unterschiedliche Kombinationen von Ergebnissen und Fortschritt.

 

Tabelle, die unterschiedliche Kombinationen von Ergebnissen und Fortschritt darstellt   
Abbildung 2. Unterschiedliche Kombinationen von Ergebnissen und Fortschritt

Die Bereitstellung wichtiger asynchroner Primitive in der Windows-Runtime ermöglicht, mit C#, Visual Basic, C++ und JavaScript asynchrone WinRT-Vorgänge auf gewohnte Art zu erstellen.

Hinweis: Der Schritt vom Kaltstart zum Warmstart

In der auf der //build vorgestellten Windows 8 Developer Preview wurden sämtliche asynchronen Vorgänge in WinRT kalt gestartet. Sie wurden nicht unmittelbar ausgeführt. Sie mussten entweder explizit vom Entwickler oder implizit durch Verwendung der asynchronen Funktionen in C#, Visual Basic, JavaScript oder C++ gestartet werden. Wir erhielten aus verschiedenen Quellen das Feedback, dass dies nicht intuitiv war und zu Problemen und Verwirrung führte.

In der Windows 8 Consumer Preview werden Sie bemerken, dass die asynchronen WinRT-Primitive die „Start()“-Methode nicht mehr enthalten. Ab sofort werden sämtliche asynchronen Vorgänge warm gestartet. Der Vorgang wird also von der asynchronen Methode gestartet, bevor er an die aufrufende Funktion zurückgegeben wird.

Dies ist nur eine der vielen Änderungen in der Consumer Preview, bei denen wir mithilfe Ihres Feedbacks zahlreiche kleine Verbesserungen an der Entwicklerplattform vorgenommen haben.

Funktionsweise: Ergebnisse, Abbrechen und Fehler mithilfe asynchroner Primitive

Ein asynchroner Vorgang beginnt im „Started“-Status und kann mit einem von drei weiteren Status fortgesetzt werden: „Canceled“, „Completed“ und „Error“. Der aktuelle Status eines asynchronen Vorgangs ist in der „Status“-Eigenschaft des asynchronen Vorgangs enthalten (mithilfe des Enumerationstyps AsyncStatus).

Bei einem asynchronen Vorgang kommt zunächst ein „Completed“-Handler zum Einsatz. Die Ergebnisse können aus dem „Completed“-Handler abgerufen und verwendet werden.

IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> op;
op = client.RetrieveFeedAsync(feedUri);

op.Completed = (info, status) =>
{
SyndicationFeed feed = info.GetResults();
UpdateAppWithFeed(feed);
};

Gelegentlich werden Sie einen Vorgang abbrechen wollen. Rufen Sie dazu im asynchronen Vorgang die „Cancel“-Methode auf.

IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> op;
op = client.RetrieveFeedAsync(feedUri);
op.Cancel();

Der „Completed“-Handler wird für einen asynchronen Vorgang immer benötigt, unabhängig davon, ob dieser abgeschlossen oder abgebrochen wurde bzw. einen Fehler verursacht hat. Rufen Sie GetResults nur dann auf, wenn der asynchrone Vorgang abgeschlossen wurde (und nicht, wenn er zum Beispiel abgebrochen wurde bzw. einen Fehler verursacht hat). Sie können dies durch Überprüfung des Statusparameters feststellen.

IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> op;
op = client.RetrieveFeedAsync(feedUri);
op.Cancel();

op.Completed = (info, status) =>
{
if (status == AsyncStatus.Completed)
{
SyndicationFeed feed = info.GetResults();
UpdateAppWithFeed(feed);
}
else if (status == AsyncStatus.Canceled)
{
// Operation canceled
}
};

Wenn der Vorgang fehlschlägt, ist dieser Code noch nicht stabil. Wie auch das Abbrechen werden Fehlerberichte durch denselben asynchronen Vorgang unterstützt. AsyncStatus wird zu Unterscheidung zwischen „Completed“ und „Canceled“ ebenso verwendet, wie zum Ermitteln des Fehlschlagens eines asynchronen Vorgangs (z. B. AsyncStatus.Error). Den spezifischen Fehlercode finden Sie in der ErrorCode-Eigenschaft des asynchronen Vorgangs.

op.Completed = (info, status) =>
{
if (status == AsyncStatus.Completed)
{
SyndicationFeed feed = info.GetResults();
UpdateAppWithFeed(feed);
}
else if (status == AsyncStatus.Canceled)
{
// Operation canceled
}
else if (status == AsyncStatus.Error)
{
// Error occurred, Report error
}
};

Funktionsweise: Fortschrittsbericht mithilfe asynchroner Primitive

Einige asynchrone Vorgänge in WinRT stellen Fortschrittsbenachrichtigungen bereit, während sie ausgeführt werden. Sie können diese Benachrichtigungen für aktuelle Fortschrittsberichte zum asynchronen Vorgang an den Benutzer verwenden.

In WinRT erfolgen Fortschrittsberichte über die Schnittstellen IAsyncActionWithProgress<TProgress> und IAsyncActionWithProgress<TResult, TProgress>. Jede Schnittstelle enthält ein Fortschrittsereignis, mit dem Sie Fortschrittsberichte zum asynchronen Vorgang abrufen können.

Das entsprechende Programmiermodell ist nahezu identisch mit der Verwendung des „Completed“-Ereignisses. In unserem Beispiel zum Abrufen von Syndication-Feeds können wir berichten, wie viele Bytes der Gesamtsumme bereits abgerufen wurden:

 

op = client.RetrieveFeedAsync(feedUri);

float percentComplete = 0;
op.Progress = (info, progress) =>
{
percentComplete = progress.BytesRetrieved /
(float)progress.TotalBytesToRetrieve;
};

In diesem Fall gehört der zweite Parameter (progress) zum Typ RetrievalProgress. Dieser Typ enthält zwei Parameter (bytesRetrieved und totalBytesToRetrieve) mit denen aktuelle Fortschritte berichtet werden.

Ausführlichere Informationen (zum asynchronen Programmieren in WinRT oder zu Asynchronität im Allgemeinen) finden Sie unter:

Zusammenfassung

Das Interessante an der herausragenden Bedeutung von Asynchronität in WinRT sind ihre Auswirkungen auf die Art und Weise, wie Sie Code strukturieren und die Architektur von Apps erstellen. Sie werden Apps als unabhängiger betrachten. Wir würden uns über Ihr Feedback zu diesem Thema freuen! Schreiben Sie dazu einfach hier oder in den Foren des Entwicklungscenters einen Kommentar.

Wir möchten, dass Benutzer Ihre Windows-8-Apps lieben. Wir möchten, dass diese Apps beim Starten ebenso rasch ausgeführt werden wie bei der weiteren Verwendung. Und wir möchten es Ihnen noch einfacher machen, solche Apps zu entwickeln. Wir möchten es Ihnen erleichtern, Verbindungen mit den Websites sozialer Netzwerke herzustellen, Daten in der Cloud zu speichern, mit Dateien auf der Festplatte zu arbeiten, mit anderen Gadgets sowie Geräten zu kommunizieren usw., während Sie großartige Apps entwickeln.

 

– Jason Olson

Programmmanager, Windows

  • Loading...
Leave a Comment
  • Please add 3 and 8 and type the answer here:
  • Post