Domanda

Alcune API, come WebClient, utilizzano il Pattern asincrono basato su eventi . Anche se questo sembra semplice e probabilmente funziona bene in un'app vagamente accoppiata (ad esempio BackgroundWorker in un'interfaccia utente), non si lega molto bene insieme.

Ad esempio, ecco un programma multithread in modo che il lavoro asincrono non si blocchi. (Immagina che ciò avvenga in un'app server e venga chiamato centinaia di volte: non vuoi bloccare i thread ThreadPool.) Otteniamo 3 variabili locali ("quot" e ";), quindi facciamo 2 chiamate asincrone, con il risultato di il primo alimenta la seconda richiesta (quindi non possono andare in parallelo). Anche lo stato potrebbe mutare (facile da aggiungere).

Usando WebClient, le cose finiscono come segue (o finisci per creare un mucchio di oggetti per agire come chiusure):

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();
    }

}

Esiste un modo generico per riformattare questo modello asincrono basato su eventi? (Ad esempio, non è necessario scrivere metodi di estensione dettagliati per ciascuna API in questo modo?) BeginXXX ed EndXXX lo rendono semplice, ma questo modo di eventi non sembra offrire in alcun modo.

È stato utile?

Soluzione

Potresti voler esaminare F # . F # può automatizzare questa codifica per te con il suo flusso di lavoro & # 171; & # 187; caratteristica. La presentazione PDC '08 di F # ha trattato richieste web asincrone usando un flusso di lavoro di libreria standard chiamato async , che gestisce il BeginXXX / EndXXX , ma puoi scrivere un flusso di lavoro per il modello di eventi senza troppe difficoltà o trovarne uno fisso. E F # funziona bene con C #.

Altri suggerimenti

In passato l'ho implementato usando un metodo iteratore: ogni volta che vuoi un altro URL richiesto, usi " yield return " per restituire il controllo al programma principale. Una volta terminata la richiesta, il programma principale richiama nuovamente il tuo iteratore per eseguire il prossimo lavoro.

Stai effettivamente usando il compilatore C # per scrivere una macchina a stati per te. Il vantaggio è che puoi scrivere un codice C # dall'aspetto normale nel metodo iteratore per guidare il tutto.

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();
    }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top