Основанная на событиях асинхронность в C #; возможен ли общий рефакторинг? - PullRequest
2 голосов
/ 29 октября 2008

Некоторые API, такие как WebClient, используют Асинхронный шаблон на основе событий . Хотя это выглядит просто и, вероятно, хорошо работает в слабо связанных приложениях (скажем, BackgroundWorker в пользовательском интерфейсе), оно не очень хорошо сцепляется.

Например, вот многопоточная программа, поэтому асинхронная работа не блокируется. (Представьте, что это происходит в серверном приложении и вызывается сотни раз - вы не хотите блокировать ваши потоки ThreadPool.) Мы получаем 3 локальные переменные («состояние»), а затем делаем 2 асинхронных вызова с результатом первая подача во второй запрос (чтобы они не могли идти параллельно). Состояние тоже может видоизменяться (легко добавить).

Используя WebClient, все заканчивается примерно так (или вы создаете группу объектов, которые действуют как замыкания):

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

}

Существует ли общий способ реорганизации этого асинхронного шаблона на основе событий? (То есть не нужно писать подробные методы расширения для каждого API, как это?) BeginXXX и EndXXX упрощают эту задачу, но этот способ обработки событий, кажется, не предлагает никакого способа.

Ответы [ 2 ]

4 голосов
/ 29 октября 2008

В прошлом я реализовывал это с помощью метода итератора: каждый раз, когда вам требуется другой URL-адрес, вы используете «yield return», чтобы передать управление обратно основной программе. Как только запрос завершится, основная программа перезвонит в ваш итератор для выполнения следующей части работы.

Вы эффективно используете компилятор C # для написания конечного автомата для вас. Преимущество состоит в том, что вы можете написать нормально выглядящий код C # в методе итератора, чтобы управлять всем этим.

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();
    }
}
1 голос
/ 13 марта 2009

Возможно, вы захотите посмотреть на F#. F# может автоматизировать это кодирование для вас с помощью функции «рабочего процесса». Представление '08 PDC F# касалось асинхронных веб-запросов с использованием стандартного рабочего процесса библиотеки под названием async, который обрабатывает шаблон BeginXXX / EndXXX, но вы можете без труда написать рабочий процесс для шаблона событий, или найти консервированный. И F # хорошо работает с C #.

...