Frage

Einige APIs, wie die WebClient, verwenden Sie die ereignisbasierte Async Muster . Während dies einfach aussieht, und wahrscheinlich funktioniert gut in einer lose gekoppelten app (sagen wir, Background in einem UI), spielt es keine Kette zusammen sehr gut.

Zum Beispiel, hier ist ein Programm, das multithreaded ist so die Asynchron Arbeit nicht blockiert. (Stellen Sie sich in einer Server-Anwendung gehen und rief mehrere hundert Mal - Sie wollen nicht Ihre Threadpool-Threads blockieren.) Wir bekommen drei lokale Variablen ( „state“), dann machen zwei Asynchron-Anrufe, mit dem Ergebnis der zuerst in die zweite Anforderung Fütterung (so können sie nicht parallel gehen). Staat könnte mutieren zu (leicht hinzufügen).

Mit WebClient beenden Dinge wie die folgenden (oder Ihnen eine Reihe von Objekten am Ende der Schaffung von bis zu wie Verschlüssen wirken):

using System;
using System.Net;

class Program
{
    static void onEx(Exception ex) {
        Console.WriteLine(ex.ToString());
    }

    static void Main() {
        var url1 = new Uri(Console.ReadLine());
        var url2 = new Uri(Console.ReadLine());
        var someData = Console.ReadLine();

        var webThingy = new WebClient();
        DownloadDataCompletedEventHandler first = null;
        webThingy.DownloadDataCompleted += first = (o, res1) => {
            if (res1.Error != null) {
                onEx(res1.Error);
                return;
            }
            webThingy.DownloadDataCompleted -= first;
            webThingy.DownloadDataCompleted += (o2, res2) => {
                if (res2.Error != null) {
                    onEx(res2.Error);
                    return;
                }
                try {
                    Console.WriteLine(someData + res2.Result);
                } catch (Exception ex) { onEx(ex); }
            };
            try {
                webThingy.DownloadDataAsync(new Uri(url2.ToString() + "?data=" + res1.Result));
            } catch (Exception ex) { onEx(ex); }
        };
        try {
            webThingy.DownloadDataAsync(url1);
        } catch (Exception ex) { onEx(ex); }

        Console.WriteLine("Keeping process alive");
        Console.ReadLine();
    }

}

Gibt es eine generische Art und Weise dieses ereignisbasierte asynchrone Muster zu umgestalten? (Das heißt keine detaillierten Erweiterungsmethoden für jede API, das ist so schreiben müssen?) BeginXXX und EndXXX es einfach machen, aber dieses Ereignis Weg scheint keinen Weg anbieten zu können.

War es hilfreich?

Lösung

Sie möchten vielleicht in F# suchen. F# kann für Sie mit seinem «Workflow» Merkmale diese Codierung automatisieren. Die '08 PDC Präsentation von F# mit asynchronen Web-Anfragen behandeln eine Standardbibliothek Workflow genannt async verwenden, die die BeginXXX / EndXXX Muster behandelt, aber man kann ohne große Schwierigkeiten, oder findet ein gekapselten man einen Workflow für die Ereignismuster schreiben. Und F # funktioniert gut mit C #.

Andere Tipps

In der Vergangenheit habe ich dies mit einem Iteratormethode implementiert: Jedes Mal, wenn Sie eine andere URL wollen angefordert, Sie „yield return“ verwenden Kontrolle passieren zurück zum Hauptprogramm. Sobald die Anforderung beendet hat, ruft das Hauptprogramm zurück in Ihre Iterator das nächste Stück Arbeit auszuführen.

Sie verwenden effektiv den C # -Compiler eine Zustandsmaschine für Sie zu schreiben. Der Vorteil ist, dass Sie normal aussehend C # -Code in der Iteratormethode schreiben können, die ganze Sache zu fahren.

using System;
using System.Collections.Generic;
using System.Net;

class Program
{
    static void onEx(Exception ex) {
        Console.WriteLine(ex.ToString());
    }

    static IEnumerable<Uri> Downloader(Func<DownloadDataCompletedEventArgs> getLastResult) {
        Uri url1 = new Uri(Console.ReadLine());
        Uri url2 = new Uri(Console.ReadLine());
        string someData = Console.ReadLine();
        yield return url1;

        DownloadDataCompletedEventArgs res1 = getLastResult();
        yield return new Uri(url2.ToString() + "?data=" + res1.Result);

        DownloadDataCompletedEventArgs res2 = getLastResult();
        Console.WriteLine(someData + res2.Result);
    }

    static void StartNextRequest(WebClient webThingy, IEnumerator<Uri> enumerator) {
        if (enumerator.MoveNext()) {
            Uri uri = enumerator.Current;

            try {
                Console.WriteLine("Requesting {0}", uri);
                webThingy.DownloadDataAsync(uri);
            } catch (Exception ex) { onEx(ex); }
        }
        else
            Console.WriteLine("Finished");
    }

    static void Main() {
        DownloadDataCompletedEventArgs lastResult = null;
        Func<DownloadDataCompletedEventArgs> getLastResult = delegate { return lastResult; };
        IEnumerable<Uri> enumerable = Downloader(getLastResult);
        using (IEnumerator<Uri> enumerator = enumerable.GetEnumerator())
        {
            WebClient webThingy = new WebClient();
            webThingy.DownloadDataCompleted += delegate(object sender, DownloadDataCompletedEventArgs e) {
                if (e.Error == null) {
                    lastResult = e;
                    StartNextRequest(webThingy, enumerator);
                }
                else
                    onEx(e.Error);
            };

            StartNextRequest(webThingy, enumerator);
        }

        Console.WriteLine("Keeping process alive");
        Console.ReadLine();
    }
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top