假设我们要下载网页的内容,在应用程序中使用,我们可以使用 System.Net 命名空间下的 WebClient 类,但下不能演示我们需要解决的问题,运行复杂、长时间的操作。相反,我们将首先显式创建 HTTP 请求,然后,下载数据:
var req =HttpWebRequest.Create("http://manning.com");
var resp = req.GetResponse(); <-- 初始化连接
var stream = resp.GetResponseStream();
var reader = new StreamReader(stream); <-- 下载网页
var html = reader.ReadToEnd();
Console.WriteLine(html);
这段代码虽然能运行,但远不完美,它在两个地方执行 HTTP 通讯。第一,它需要初始化与服务器的 HTTP 连接;第二,下载网页。两个操作都可能会花很长时间,每一个都可能会阻塞活动的线程,从而导致我们的应用程序没有响应。
要解决这个问题,我们可以在单独的线程中进行下载,但是,使用线程代价昂贵,所以,这种方法会限制我们能够并行运行的下载的数量;此外,大部分时间线程是等待响应,因此,没有理由浪费线程资源。要完美地解决问题,我们应使用异步编程接口,它能够触发请求,在操作完成时,执行提供的回调:
var req = HttpWebRequest.Create("http://manning.com");
req.BeginGetResponse(asyncRes1 => { <-- 启动操作
var resp =req.EndGetResponse(asyncRes1);
var stream =resp.GetResponseStream();
var reader = newStreamReader(stream);
reader.BeginReadToEnd(asyncRes2=> { <-- 不退出
var html =reader.EndReadToEnd(asyncRes2);
Console.WriteLine(html);
});
});
要写这种代码,是很难的。虽然我们使用了 C# 3.0 的 lambda 函数,代码看起来仍然很复杂。我们必须改变其结构;不能顺序地写代码,而是用嵌套的回调的序列。
前面的代码段还有另外一个问题,在 .NET 框架中并没有BeginReadToEnd 方法,因此,我们可能必须自己实现异步下载。不幸的是,不可能使用简单的顺序代码实现,因为我们需要以缓冲的方式下载页面。如果以异步风格写这段代码(使用嵌套的回调),我们不可能使用任何内置的结构,例如,while 循环。
我们将会看到,异步工作流能解决所有在写下载程序时遇到的问题,能够以通常的顺序方式写代码,使用标准的控制结构,比如递归,或者甚至是 while 循环。代码异步执行,因此,工作流等待操作完成,而不需要使用专门的线程。在下一节,我们将学习如何使用 F# 异步工作流,来实现我们刚讨论的示例。