Question

Certaines API, comme WebClient, utilisent le Modèle async basé sur des événements . Bien que cela paraisse simple et fonctionne probablement bien dans une application faiblement couplée (par exemple, BackgroundWorker dans une interface utilisateur), cela ne l'enchaîne pas très bien.

Par exemple, voici un programme multithread afin que le travail asynchrone ne bloque pas. (Imaginez que cela se passe dans une application serveur et soit appelé des centaines de fois - vous ne voulez pas bloquer vos threads ThreadPool.) Nous obtenons 3 variables locales ("état"), puis faisons 2 appels asynchrones, avec le résultat de le premier alimentant la deuxième demande (afin qu'ils ne puissent pas aller en parallèle). L’État pourrait aussi muter (facile à ajouter).

Avec WebClient, les choses se présentent comme suit (ou vous créez un tas d’objets qui agissent comme des fermetures):

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

}

Existe-t-il un moyen générique de refactoriser ce modèle asynchrone basé sur les événements? (C’est-à-dire qu’il n’est pas nécessaire d’écrire des méthodes d’extension détaillées pour chaque API comme celle-ci?) BeginXXX et EndXXX facilitent les choses, mais ce moyen d’événement ne semble offrir aucun moyen.

Était-ce utile?

La solution

Vous voudrez peut-être examiner F # . F # peut automatiser ce codage avec sa fonctionnalité «workflow». La présentation PDC '08 de F # traitait les requêtes Web asynchrones à l'aide d'un flux de travail de bibliothèque standard appelé async , qui gère le BeginXXX / EndXXX , mais vous pouvez écrire un flux de travail pour le modèle d’événement sans trop de difficulté ou en trouver un en conserve. Et F # fonctionne bien avec C #.

Autres conseils

Dans le passé, j’avais implémenté cette méthode à l’aide d’une méthode itérateur: chaque fois que vous souhaitez demander une autre URL, vous utilisez le paramètre "return return". pour renvoyer le contrôle au programme principal. Une fois la demande terminée, le programme principal rappelle votre itérateur pour exécuter le travail suivant.

Vous utilisez efficacement le compilateur C # pour écrire une machine à états pour vous. L’avantage est que vous pouvez écrire du code C # à l’apparence normale dans la méthode itérateur pour piloter l’ensemble.

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();
    }
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top