2

一些 API,如 WebClient,使用基于事件的异步模式。虽然这看起来很简单,并且可能在松散耦合的应用程序中运行良好(例如 UI 中的 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 使它变得容易,但这种事件方式似乎没有提供任何方式。

4

2 回答 2

4

在过去,我使用迭代器方法实现了这一点:每次您想要请求另一个 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();
    }
}
于 2008-10-29T10:13:04.657 回答
1

你可能想调查一下F#F#可以通过其«工作流程»功能为您自动执行此编码。'08 PDC 演示文稿F#使用名为 的标准库工作流处理异步 Web 请求async,该工作流处理BeginXXX/EndXXX模式,但您可以轻松地为事件模式编写工作流,或者找到一个固定的工作流。F# 与 C# 配合得很好。

于 2009-03-13T16:23:24.717 回答