将 Task 包装在 Async 中的目的是为了更轻松地与其他异步组合它,或者在 async { ... } 块内将其与 let! 一起使用。而在后一种情况下,包裹的任务在其封闭的async { ... } 块启动之前不会启动。
例如,我们看下面的函数:
let printTask str =
async {
printfn "%s" str
} |> Async.StartAsTask
这没什么用;它存在的唯一原因是您可以知道它何时开始运行,因为它会在屏幕上打印一条消息。如果您从 F# Interactive 调用它:
printTask "Hello"
您将看到以下输出:
Hello
val it : Threading.Tasks.Task<unit> =
System.Threading.Tasks.Task`1[Microsoft.FSharp.Core.Unit]
{AsyncState = null;
CreationOptions = None;
Exception = null;
Id = 4;
IsCanceled = false;
IsCompleted = true;
IsCompletedSuccessfully = true;
IsFaulted = false;
Status = RanToCompletion;}
所以它打印“Hello”然后返回完成的任务。这证明Task是立即启动的。
但是现在看下面的代码:
open System.Net
open System
open System.IO
let printTask str =
async {
printfn "%s" str
} |> Async.StartAsTask
let fetchUrlAsync url =
async {
let req = WebRequest.Create(Uri(url))
do! printTask ("started downloading " + url) |> Async.AwaitTask
use! resp = req.GetResponseAsync() |> Async.AwaitTask
use stream = resp.GetResponseStream()
use reader = new IO.StreamReader(stream)
let html = reader.ReadToEnd()
do! printTask ("finished downloading " + url) |> Async.AwaitTask
}
(这是Scott Wlaschin's "Async Web Downloader" example,适用于在内部使用任务而不是异步)。
这里,async { ... } 块包含三个任务,所有这些任务都包装在 Async.AwaitTask 中。 (请注意,如果您从这些行中删除了|> Async.AwaitTask,则会收到类型错误)。对于每个任务,一旦其代码行执行,它将立即启动。但这是很重要的一点,因为整个async { ... } 计算不是立即开始的。所以我可以这样做:
let a = fetchUrlAsync "http://www.google.com"
在 F# Interactive 中打印的唯一内容是 val a : Async<unit>。我想等多久就等多久,不会打印任何其他内容。只有当我真正开始 a 它才会开始运行:
a |> Async.RunSynchronously
这会立即打印started downloading http://www.google.com,然后在短暂的暂停后打印finished downloading http://www.google.com。
这就是Async.AwaitTask 的目的:允许async { ... } 块更轻松地与返回任务的C# 代码进行互操作。如果 Async.AwaitTask 调用在 async { ... } 块内,那么在封闭的 Async 启动之前,任务实际上不会启动,因此您仍然可以获得“冷启动”异步的所有优势。