Pergunta

Algumas APIs, como o WebClient, use o padrão assíncrono baseado em evento href="http://msdn.microsoft.com/en-us/library/wewwczdw.aspx" rel="nofollow noreferrer"> . Enquanto isto parece simples, e provavelmente funciona bem em um aplicativo de baixo acoplamento (digamos, BackgroundWorker em um UI), que não encadear muito bem.

Por exemplo, aqui está um programa que é multithreaded para que o trabalho assíncrono não bloqueia. (Imagine isso está acontecendo em um aplicativo de servidor e chamou centenas de vezes -. Você não deseja bloquear seus segmentos ThreadPool) Ficamos com 3 variáveis ??locais ( "state"), então fazer 2 chamadas assíncronas, com o resultado da primeira alimentação para o segundo pedido (de modo que não pode ir paralelo). Estado possa sofrer mutação também (fácil adicionar).

Usando WebClient, as coisas acabam como o seguinte (ou você acabar criando um monte de objetos de agir como tampas):

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 uma maneira genérica para refatorar esse padrão assíncrono baseado em eventos? (Ou seja, não tem que escrever métodos de extensão detalhada para cada isso é API como este?) BeginXXX e EndXXX torná-lo fácil, mas desta forma evento não parecem oferecer qualquer forma.

Foi útil?

Solução

Você pode querer olhar para F#. F# pode automatizar esta codificação para você com a sua «fluxo de trabalho» recurso. A apresentação '08 PDC de F# tratado requisições web assíncronas usando uma biblioteca padrão de fluxo de trabalho chamado async, que lida com o padrão BeginXXX / EndXXX, mas você pode escrever um fluxo de trabalho para o padrão evento sem muita dificuldade, ou encontrar um enlatada. E F # funciona bem com C #.

Outras dicas

No passado, eu tenho implementado isso usando um método iterator: cada vez que você quer outra URL solicitado, você usa "yield return" para passar o controle de volta ao programa principal. Uma vez que a solicitação de acabamentos, as principais chamadas de programa de volta em seu iterador para executar a próxima peça de trabalho.

Você está usando efetivamente o compilador C # para escrever uma máquina de estado para você. A vantagem é que você pode escrever código de aparência normal C # no método iterator para dirigir a coisa toda.

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