Pregunta

Algunas API, como el WebClient, usan Patrón de Async basado en eventos . Si bien esto parece simple, y probablemente funcione bien en una aplicación poco acoplada (por ejemplo, BackgroundWorker en una UI), no se encadena muy bien.

Por ejemplo, aquí hay un programa multiproceso para que no se bloquee el trabajo asíncrono. (Imagina que esto va en una aplicación de servidor y se llama cientos de veces; no quieres bloquear tus hilos de ThreadPool). Obtenemos 3 variables locales (" estado "), luego hacemos 2 llamadas asíncronas, con el resultado de la primera alimentación en la segunda solicitud (por lo que no pueden ir en paralelo). El estado también podría mutar (fácil de agregar).

Al usar WebClient, las cosas terminan de la siguiente manera (o terminas creando un montón de objetos para que actúen como cierres):

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

}

¿Hay una forma genérica de refactorizar este patrón asíncrono basado en eventos? (Es decir, no tengo que escribir métodos de extensión detallados para cada API así). BeginXXX y EndXXX lo hacen fácil, pero este evento no parece ofrecer ninguna forma.

¿Fue útil?

Solución

Es posible que desee ver F # . F # puede automatizar esta codificación para usted con su & # 171; workflow & # 187; característica. La presentación '08 PDC de F # se ocupó de las solicitudes web asíncronas utilizando un flujo de trabajo de biblioteca estándar denominado async , que maneja el BeginXXX / EndXXX , pero puede escribir un flujo de trabajo para el patrón de evento sin mucha dificultad, o encontrar uno enlatado. Y F # funciona bien con C #.

Otros consejos

En el pasado, lo he implementado utilizando un método de iterador: cada vez que desea solicitar otra URL, usa " rendimiento de retorno " para pasar el control de nuevo al programa principal. Una vez que la solicitud finaliza, el programa principal vuelve a llamar a su iterador para ejecutar el siguiente trabajo.

Estás utilizando efectivamente el compilador de C # para escribir una máquina de estado para ti. La ventaja es que puede escribir código C # de apariencia normal en el método iterador para manejar todo.

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();
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top