C#中基于事件的异步;任何通用的重构都可能吗?

时间:2021-07-05 20:56:10

Some APIs, like the WebClient, use the Event-based Async pattern. While this looks simple, and probably works well in a loosely coupled app (say, BackgroundWorker in a UI), it doesn't chain together very well.

某些API(如WebClient)使用基于事件的异步模式。虽然这看起来很简单,并且可能在松散耦合的应用程序(例如,UI中的BackgroundWorker)中运行良好,但它并不能很好地链接在一起。

For instance, here's a program that's multithreaded so the async work doesn't block. (Imagine this is going in a server app and called hundreds of times -- you don't want to block your ThreadPool threads.) We get 3 local variables ("state"), then make 2 async calls, with the result of the first feeding into the second request (so they can't go parallel). State could mutate too (easy to add).

例如,这是一个多线程的程序,因此异步工作不会阻塞。 (想象一下,这是在一个服务器应用程序中,并且被调用了数百次 - 你不想阻止你的ThreadPool线程。)我们得到3个局部变量(“状态”),然后进行2次异步调用,结果是首先进入第二个请求(因此他们不能并行)。国家也可以变异(容易添加)。

Using WebClient, things end up like the following (or you end up creating a bunch of objects to act like closures):

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

}

Is there an generic way to refactor this event-based async pattern? (I.e. not have to write detailed extension methods for each API thats like this?) BeginXXX and EndXXX make it easy, but this event way doesn't seem to offer any way.

是否有一种通用的方法来重构这种基于事件的异步模式? (即,不必为每个这样的API编写详细的扩展方法?)BeginXXX和EndXXX使它变得简单,但这种事件方式似乎没有提供任何方式。

2 个解决方案

#1


1  

You might want to look into F#. F# can automate this coding for you with its «workflow» feature. The '08 PDC presentation of F# dealt with asynchronous web requests using a standard library workflow called async, which handles the BeginXXX/EndXXX pattern, but you can write a workflow for the event pattern without much difficulty, or find a canned one. And F# works well with C#.

你可能想看看F#。 F#可以通过«工作流»功能为您自动编码。 F#的'08 PDC演示文稿使用名为async的标准库工作流处理异步Web请求,该工作流处理BeginXXX / EndXXX模式,但您可以毫不费力地为事件模式编写工作流,或者找到一个罐装工作流。 F#适用于C#。

#2


4  

In the past I've implemented this using an iterator method: every time you want another URL requested, you use "yield return" to pass control back to the main program. Once the request finishes, the main program calls back into your iterator to execute the next piece of work.

在过去,我使用迭代器方法实现了这一点:每次要求另一个URL时,使用“yield return”将控制权传递回主程序。请求完成后,主程序将回调您的迭代器以执行下一项工作。

You're effectively using the C# compiler to write a state machine for you. The advantage is that you can write normal-looking C# code in the iterator method to drive the whole thing.

您正在有效地使用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


1  

You might want to look into F#. F# can automate this coding for you with its «workflow» feature. The '08 PDC presentation of F# dealt with asynchronous web requests using a standard library workflow called async, which handles the BeginXXX/EndXXX pattern, but you can write a workflow for the event pattern without much difficulty, or find a canned one. And F# works well with C#.

你可能想看看F#。 F#可以通过«工作流»功能为您自动编码。 F#的'08 PDC演示文稿使用名为async的标准库工作流处理异步Web请求,该工作流处理BeginXXX / EndXXX模式,但您可以毫不费力地为事件模式编写工作流,或者找到一个罐装工作流。 F#适用于C#。

#2


4  

In the past I've implemented this using an iterator method: every time you want another URL requested, you use "yield return" to pass control back to the main program. Once the request finishes, the main program calls back into your iterator to execute the next piece of work.

在过去,我使用迭代器方法实现了这一点:每次要求另一个URL时,使用“yield return”将控制权传递回主程序。请求完成后,主程序将回调您的迭代器以执行下一项工作。

You're effectively using the C# compiler to write a state machine for you. The advantage is that you can write normal-looking C# code in the iterator method to drive the whole thing.

您正在有效地使用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();
    }
}