Asynchrone Programmierung in WinRT

Meine neue WebSite finden Du jetzt unter https://AttilaKrick.com.

Beispiele zu Asynchrone API

MSDN Asynchrone Programmierung

Ein Benutzer erwartet das auf seine Aktion unmittelbare Reaktion von der App erfolgt. Um so verwirrter reagiert der Benutzer wenn diese Reaktion auf sich warten lässt weil die App noch synchron mit dem laden einer Datei aus dem Internet blockiert ist. Eine flüssige App würde hier den Download asynchron durchführen, so das der Benutzer weiter mit seiner App interagieren kann.

Wer mit dem asynchronen Mustern nicht vertraut ist kann sich die Asynchronität wie eine Bestellung beim Pizza-Dienst vorstellen. Sie rufen an und geben Ihre Bestellung durch und können anschließend Ihre bisherige Tätigkeit fortsetzen. Sobald der Pizza-Man oder -Frau mit der fertig Pizza klingelt können Sie sich nun um das leckere Esswerk kümmern. Niemand würde auf die Idee kommen nach der Bestellung apathisch in Inaktivität zu verfallen bis es klingelt.

Async-Namenskonvention – Üblicherweise enden asynchrone Methoden auf ...Async. Diese Methoden finden Sie dort wo Dauer eine Rolle spielt, z.B.

ContactPicker.PickSingleContactAsync
XmlDocument.SaveToFileAsync
DataReader.LoadAsync
RandomAccessStream.CopyAsync
SyndicationClient.RetrieveFeedAsync

await & async – Wenn Sie den await-Operator in einer Methode verwenden muss diese Methode das async-Schlüsselwort enthalten. Im Konstruktor oder in einer Eigenschaft können Sie await nicht verwenden. TIPP Erstellen Sie eine Methode, lagern den await-Code dorthin aus und ruf anschließend die erstellte Methode im Konstruktor bzw. in der Eigenschaft auf.

Threads, Kontextwechsel und Verteiler spielen für Sie keine Rolle mehr. Der Aufruf einer asynchronen API mittels await erfolgt im selben Kontext wie der ursprüngliche Aufruf. Das bedeutet dass Sie die Benutzeroberfläche mit den Ergebnissen aktualisieren können ohne sich um die Rückkehr zum Benutzeroberflächen-Thread zu kümmern.

Task – Sie können aus dem .NET Framework Task und Task<TResult>
verwenden um eigene asynchrone Methode zu implementieren. Hierzu gibt es die Erweiterungsmethoden (WindowsRuntimeSystemExtensions) AsAsyncAction oder AsAsyncOperation<TResult>. Diese liefern Async-WinRT-Konforme Ergebnisse die sich in WinRT entsprechend weiter verwerten lassen. Hierzu später mehr.

Asynchrone Methoden verwenden

Angenommen wir wollen Nachrichten-Beiträge direkt aus dem Internet herunterladen. Hierbei ist es wichtig dass die App reaktionsfähig bleibt. Um diese Reaktionsfähigkeit sicherzustellen, stellt WinRT eine asynchrone Methode SyndicationClient.RetrieveFeedAsync zum Herunterladen von Newsfeeds bereit deren asynchrone Verwendung wie folgt aussieht:

async void Button_Click(object sender, RoutedEventArgs e)
{
    var feedUri = new Uri("http://rss.golem.de/rss.php?feed=RSS2.0");
    var client = new SyndicationClient();
    try
    {
        var feed = await client.RetrieveFeedAsync(feedUri);
        StatusTextBox.Text = feed.Title.Text;
    }
    catch (Exception ex)
    {
        StatusTextBox.Text = ex.Message;
    }
}

Anmerkungen

  1. Der await-Operator teilt dem Compiler mit dass Sie eine asynchrone Methode aufrufen und er zusätzliche Arbeiten für Sie erliegen muss.
  2. Das Schlüsselwort asyncmuss in der Methodendeklaration aller Methoden angegeben werden, in denen Sie den await-Operator verwenden.
  3. Durch den Aufruf von ... await client.RetrieveFeedAsync initiiert die Methode den Abruf asynchron und beendet den Click-Ereignishandler vorübergehend. Ihre App kann dadurch andere Ereignisse verarbeiten und bleibt für den Benutzer reaktionsfähig.
  4. Wenn RetrieveFeedAsync abgeschlossen und das Ergebnis SyndicationFeed verfügbar ist macht Ihre App im Click-Ereignishandler an der Stelle weiter wo sie aufgehört hat var feeds ... um den Rest des Handlers abzuarbeiten.
  5. Während des asynchronen Vorgangs könnte z.B. die Netzwerkverbindung zum RSS-Feed abbrechen. Daher können Sie wie gewohnt die Fehlertoleranz erhöhen, indem Sie den asynchronen Code in einen try-catch-Block ausführen.

Ergebnisse asynchroner Methoden

Der Rückgabetyp von RetrieveFeedAsync ist kein SyndicationFeed sondern IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress>. Es ist wichtig zu Verstehen das der await-Operator tatsächlich auf Basis des Rückgabewerts und nicht auf der Methode agiert. Wenn Sie await anwenden erhalten Sie das Ergebnis der asynchronen Methoden SyndicationFeed, wenn Sie await nicht anwenden erhalten Sie IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress>.

Wenn Sie eine asynchrone Methode verwenden reflektieren Sie üben den Rückgabetyp um Infos zum Ergebnis zu erhalten. Alle asynchronen Methoden in WinRT geben einen der folgenden Typen zurück:

IAsyncAction                                    = Einfache Aktion
IAsyncActionWithProgress<TProgress>             = inkl Fortschritt 
IAsyncOperation<TResult>                        = inkl Ergebnis
IAsyncOperationWithProgress<TResult, TProgress> = inkl Ergebnis + Forts.

Diese Rückgabetypen eröffnen Ihnen weitere Möglichkeiten beim Umgang mit asynchronen Vorgängen die im weiteren Verlauf besprochen werden.

Ablaufsteuerung mithilfe asynchroner Rückgabetypen

Status

Ein asynchroner Vorgang beginnt im .Status==Started und wird mit Canceled, Completed oder Error fortgesetzt.

var feedUri = new Uri("http://rss.golem.de/rss.php?feed=RSS2.0");
var client = new SyndicationClient();

var asyncInfo = client.RetrieveFeedAsync(feedUri);
var id = asyncInfo.Id;
var status = asyncInfo.Status;

Abbrechen

In unterschiedlichen Situation kann es nötig sein einen asynchronen Vorgang abzubrechen. Rufe Sie dazu die Cancel-Methode auf:

IAsyncOperationWithProgress<SyndicationFeed, 
    RetrievalProgress> _FeedAsyncInfo;

void Starten_Click(object sender, RoutedEventArgs e)
{
    var feedUri = new Uri("http://rss.golem.de/rss.php?feed=RSS2.0");
    var client = new SyndicationClient();

    _FeedAsyncInfo = client.RetrieveFeedAsync(feedUri);
    var status = _FeedAsyncInfo.Status; // = Started
}

void Abbrechen_Click(object sender, RoutedEventArgs e)
{
    _FeedAsyncInfo**.Cancel()**;
    var status = _FeedAsyncInfo.Status; // = Canceled
}

Abschluss

Bei einem asynchronen Vorgang wird der Completed-Handler immer benötigt da dieser u. a. das tatsächliche Ergebnis liefert aber auch ausgeführt wird wenn abgebrochen wurde oder einen Fehler auftrat. Überprüfen Sie den Status und rufen nur im Erfolgsfall über GetResults() das Ergebnis ab:

var feedUri = new Uri("http://rss.golem.de/rss.php?feed=RSS2.0");
var client = new SyndicationClient();

var asyncInfo = client.RetrieveFeedAsync(feedUri);
Meldung.Text = String.Format("Start {0:ss.fff}: ID {1} STATUS {2}\n",
    DateTime.Now,
    asyncInfo.Id,
    asyncInfo.Status);

asyncInfo.Completed = async (ai, status) =>
{
    await Dispatcher.RunIdleAsync((f) =>
    {
        if (status == AsyncStatus.Completed)
            Meldung.Text = String.Format("{0:ss.fff}: ID {1} STATUS {2} ERGEBNIS {3}\n",
                DateTime.Now,
                ai.Id,
                ai.Status,
                ai.GetResults().Title.Text);

        else if (status == AsyncStatus.Canceled)
            Meldung.Text = String.Format("Abbruch {0:ss.fff}: ID {1}\n",
                DateTime.Now,
                ai.Id);

        else if (status == AsyncStatus.Error)
            Meldung.Text = String.Format("Fehler {0:ss.fff}: ID {1} "
                + "ERROR {2}\n",
                DateTime.Now,
                ai.Id, 
                ai.ErrorCode);
    });
};

Der Completed-Handler wird nicht im UI-Thread ausgeführt und kann so auch nicht direkt auf UI-Elemente zugreifen. Über await Dispatcher.RunIdleAsync(...) wäre ein Zugriff auf UI-Elemente wieder möglich da diese im UI-Thread ausgeführt werden.

Fortschritt

Einige asynchrone Methoden unterstützen Fortschrittsbenachrichtigungen während sie ausgeführt werden. Sie können diese Benachrichtigungen für aktuelle Fortschrittsberichte zum asynchronen Vorgang in der UI verwenden:

var feedUri = new Uri(FeedUri.Text);
var client = new SyndicationClient();
var asyncInfo = client.RetrieveFeedAsync(feedUri);

progressBar.Value = 0;
asyncInfo.Progress = async (ai, args) =>
{
    await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
        progressBar.Value = args.BytesRetrieved 
            / args.TotalBytesToRetrieve * 100;
    });
};

Eigene Aufgaben asynchron ausführen

Neben den asynchronen WinRT-Methoden kann es hilfreich sein eigene Vorgänge mit großer Dauer asynchron in einem anderen Thread als den der Oberfläche auszuführen. Fügen Sie diese Aufgabe der Warteschlange des Threadpool hinzu. Implementieren wie oben einen Handler für Completed und werten darin im Erfolgsfall das Ergebnis aus.

Wenn nun der asynchrone Vorgang in der Warteschlange des ThreadPools abgeschlossen ist, wird der Completed-Handler aufgerufen. In diesem können Sie jedoch keine Änderung in der UI vornehmen da wir uns in einem anderen Thread befinden und würde sogar eine Wrong-Thread-Excaption kassieren. Um dies zu umgehen, kann der UI zugeordnete CoreDispatcher verwendet werden um die UI im richtigen Thread zu ändern.

Und so könnte z.B. das Click-Ereignis aussehen:

void Button_Click(object sender, RoutedEventArgs e)
{
    var asyncInfo = ThreadPool.RunAsync((a) =>
    {
        // Fake rechenintensiver Code
        new ManualResetEvent(false).WaitOne(2000);
    });
    asyncInfo.Completed = async (ai, status) =>
    {
        await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        {
            switch (status)
            {
                case AsyncStatus.Completed:
                    LogThreadPoolRunAsync.Text += String.Format("Fertig {0:ss.fff} ID {1} STATUS {2}",
                        DateTime.Now,
                        asyncInfo.Id,
                        asyncInfo.Status);
                    break;

                case AsyncStatus.Error:
                    LogThreadPoolRunAsync.Text += String.Format("Fehler {0:ss.fff} ID {1} STATUS {2}",
                        DateTime.Now,
                        asyncInfo.Id,
                        asyncInfo.Status);
                    break;

                case AsyncStatus.Canceled:
                    LogThreadPoolRunAsync.Text += String.Format("Abbruch {0:ss.fff} ID {1} STATUS {2} EX {3}",
                        DateTime.Now,
                        asyncInfo.Id,
                        asyncInfo.Status,
                        ai.ErrorCode);
                    break;
            }
        });
    };
    LogThreadPoolRunAsync.Text = String.Format("Start {0:ss.fff} "
        + "ID {1} STATUS {2} >>> ",
        DateTime.Now,
        asyncInfo.Id,
        asyncInfo.Status);
}

Das gleiche Ergebnis mit dem await-Operator würde so aussehen:

async void Button_Click(object sender, RoutedEventArgs e)
{
    LogThreadPoolRunAsync.Text = String.Format("Start {0:ss.fff} >>> ", DateTime.Now);
    try
    {
        await ThreadPool.RunAsync((a) =>
        {
            // Fake rechenintensiver Code
            new ManualResetEvent(false).WaitOne(2000); 
        });
        LogThreadPoolRunAsync.Text += String.Format("Fertig {0:ss.fff}", DateTime.Now);
    }
    catch (Exception ex)
    {
        LogThreadPoolRunAsync.Text +=String.Format("Fehler {0:ss.fff} {1}", DateTime.Now, ex);
    }
}

Task nach WinRT-Async umwandeln

Um Ihre vorhandenen Task-Objekte im WinRT-Async-Model nutzen zu können oder über den Umweg der Task-Klasse an weitere Threading-Techniken wie z.B. den Cancel-Token zu gelangen, können Sie Task weiter verwenden und diese in die oben beschriebenen Async-Rückgabetypen umwandeln.

Hierfür enthält das System.Runtime.WindowsRuntime-Assembly Erweiterungsmethoden AsAsyncAction und AsAsyncOperation die Sie bei Task und Task<TResult>-Objekten nutzen können:

async void Button_Click(object sender, RoutedEventArgs e)
{
    LogTaskToAsyncOperationTest.Text = "Läuft ...";
    LogTaskToAsyncOperationTest.Text = await TaskToAsyncOperationAsync();
}

IAsyncOperation<string> TaskToAsyncOperationAsync()
{
    var t = Task<string>.Run(() =>
        {
            // Fake rechenintensiver Code
            new ManualResetEvent(false).WaitOne(3000);
            return "Nach 3s 43 asynchron heruntergeladen.";
        });
    return t.AsAsyncOperation();
}

Eigene erweiterte asynchrone Aufgaben ausführen

Das Umwandeln von Task mittels AsAsyncAction und AsAsyncOperation bietet grundlegende Unterstützung für Task’s in WinRT-Async. Darüber hinaus erhalten Sie erweiterter Unterstützung, wenn Sie AsyncInfo.Run und dessen Überladungen verwenden. Dazu gehören:

Abbruch

Mit AsyncInfo.Run werden Abbrüche mit asynchronen WinRT-Methoden unterstützt:

void Start_Click(object sender, RoutedEventArgs e)
{
    _JetztArbeitenAsyncInfo = JetztArbeitenAsync();
    _JetztArbeitenAsyncInfo.Completed = (asyncInfo, asyncStatus) => 
    {
        if (asyncStatus== AsyncStatus.Completed)
            LogAsyncInfoRunCancelTest.Text += String.Format("STATUS {0} "
                + "RESULT {1}",
                asyncStatus, asyncInfo.GetResults());

        else if (asyncStatus == AsyncStatus.Canceled)
            LogAsyncInfoRunCancelTest.Text += String.Format("STATUS {0}",
                asyncStatus);
    };
    LogAsyncInfoRunCancelTest.Text = String.Format("START {0} "
        + "STATUS {1} >>> ",
        _JetztArbeitenAsyncInfo.Id,
        _JetztArbeitenAsyncInfo.Status);
}

void Abbrechen_Click(object sender, RoutedEventArgs e)
{
    if (_JetztArbeitenAsyncInfo!=null)
    {
        _JetztArbeitenAsyncInfo.Cancel();
    }
}

private IAsyncOperation<string> _JetztArbeitenAsyncInfo;

public static IAsyncOperation<string> JetztArbeitenAsync()
{
    return AsyncInfo.Run(cancellationToken =>
    {
        return Task<string>.Run(() =>
        {
            for (int i = 0; i < 100; i++)
            {
                // Fake rechenintensiver Code
                new ManualResetEvent(false).WaitOne(25);

                if (cancellationToken.IsCancellationRequested)
                {
                    // Operation wurde abgebrochen
                    // Code der Aufräumen
                    cancellationToken.ThrowIfCancellationRequested();
                }
            }
            return "Fertig mit Async-Operation";
        });
    });
}

Fortschritt

AsyncInfo.Run stellt Unterstützung für Fortschrittsberichte durch asynchrone WinRT-Methoden bereit:

void Button_Click(object sender, RoutedEventArgs e)
{
    var ai = JetztArbeitenMitFortschrittAsync();
    ai.Progress = (asyncInfo, progressInfo) =>
    {
        LogAsyncInfoRunFortschrittTest.Text += String.Format("{0}% ",
            progressInfo);
    };
    LogAsyncInfoRunFortschrittTest.Text = String.Format("START {0} "
        + "STATUS {1} >>> Fortschritt ",
        ai.Id,
        ai.Status);
}

public static IAsyncOperationWithProgress<string, int> JetztArbeitenMitFortschrittAsync()
{
  return AsyncInfo.Run((CancellationToken cancellationToken, IProgress<int> progress) =>
    {
        return Task<string>.Run(() =>
        {
            for (int i = 0; i <= 10; i++)
            {
                progress.Report(i*10);

                // Fake rechenintensiver Code
                new ManualResetEvent(false).WaitOne(200);
            }
            return "Fertig mit Async-Operation";
        });
    });
}

App Studio

AppStudio Start

Kapitel Tile-Benachrichtigung und geöffnetes Beispiel-FlyoutKapitel SplashScreenSuchen nach SchlagwörternTool Viewer und Picker für FarbenTool Viewer und Picker für Glyphen aus der Schrift Segoe UI SymbolTool Viewer und Picker für AppBar-ButtonsTool Viewer aller AppIn-BeispieleViewer Illustrationen, Diagramme und Grafiken

Prolog

Was früher ein Fachbuch war ist heute App Studio die App. Pures Wissen auf den Punkt gebracht. Viele bunte Illustrationen. Interaktive Beispiele zum anfassen und experimentieren. Code einfach kopieren. Nach Schlagwörtern suchen. Überall lesen und stöbern und keinen dicken Schinken schleppen. Beispiele, Projekte, Vorlagen und Muster direkt aus der App öffnen und weiter nutzen. Ständige Updates direkt aus dem Store.

Meine neue WebSite finden Du jetzt unter https://AttilaKrick.com.

Inhalt

App Studio gibt Ihnen das Werkzeug an die Hand um Windows Store Apps zu entwerfen und im Windows Store zu veröffentlichen. Sie erfahren was Sie brauchen, wo Sie anfangen sollten und was Sie über das Erstellen wissen sollten. Die App dient als Einführung für den C# Programmierer aber auch als Nachschlagewerk für die tägliche Arbeit und das zu folgenden Themen:

Animationen               Ansichten-Status   App-Datenspeicher
Dateien und Ordner        Datenbindung       Drucken
Einstellungs-Flyout       Ressourcen         Hintergrundaufgaben
Lebenszyklus einer App    MVVM               Navigation
Gestaltung einer App      Sensoren           SpalshScreen  
Text und Typografie       Tastatur           Teilen
UI Elemente im Showroom   Touch              Video und Audio
Visual Studio 2012        Windows 8          Windows Store
WinRT und .NET            XAML               Zeichnen
Tile-, Badge-, Glyph-,    Tile- und          Toast-Benachrichtigung 
LockScreen                Suche-Funktion     u.v.m.

In App Studio direkt über die AppBar finden Sie Tools für die tägliche Arbeit wie:

Picker & Viewer für AppBar-Button Style
                    Color
                    Gylphen aus Segoe UI Symbol
                    StandardStyle
                    InApp-Beispiele

Vorlagen und Muster für Seiten-Layout
                        Schriftbild
                        Windows Store Checkliste
                        Visual Studio Handout

WCF Web Service für Test- und Debugging

DiaShow durch alle Illustrationen und Diagramme

Über den Autor

Attila Krick ist ein gestandener IT-Spezialist, Softwareentwickler, Autor und Trainer spezialisiert auf Betriebs-, Mail-, Datenbank-Systeme, .NET und Office aus dem Hause Microsoft (MCP, MCSA, MCT).

Seit 2005 ist er mit seinem Angebot für Sie tätig. Den Schwerpunkt seiner Tätigkeit gliedert sich in die Bereiche IT-Service und Beratung, Software-Entwicklung und Coaching und Schulung.

App Studio im Windows Store