Асинхронность на основе событий в C#;возможен какой-либо общий рефакторинг?

StackOverflow https://stackoverflow.com/questions/245929

Вопрос

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

Например, вот программа, которая является многопоточной, поэтому асинхронная работа не блокируется.(Представьте, что это происходит в серверном приложении и вызывается сотни раз - вы же не хотите блокировать потоки вашего ThreadPool.) Мы получаем 3 локальные переменные ("state"), затем выполняем 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 упрощают это, но этот способ события, похоже, не предлагает никакого способа.

Это было полезно?

Решение

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

Другие советы

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

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

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();
    }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top