提问人:MichaelGG 提问时间:10/29/2008 最后编辑:Kevin FairchildMichaelGG 更新时间:3/14/2009 访问量:1565
C# 中基于事件的异步;任何通用的重构都可能吗?
Event-based async in C#; any generic refactoring possible?
问:
某些 API(如 WebClient)使用基于事件的异步模式。虽然这看起来很简单,并且可能在松散耦合的应用程序(例如 UI 中的 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 使它变得容易,但这种事件方式似乎没有提供任何方法。
答:
过去,我使用迭代器方法实现了这一点:每次您想要请求另一个 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();
}
}
您可能需要查看 . 可以通过其“工作流”功能为您自动执行此编码。'08 PDC 的演示使用称为 的标准库工作流处理异步 Web 请求,该工作流处理 / 模式,但您可以毫不费力地为事件模式编写工作流,或者找到一个预制的工作流。F# 与 C# 配合得很好。F#
F#
F#
async
BeginXXX
EndXXX
评论