我知道 F# 与 C# 异步模型的主要区别在于,F# 中只有在调用 Async.RunSynchronously 类似的方法时,异步执行才会开始。而在 C# 中,当一个方法返回一个任务(Task),执行通常(不总是)会立即开始在后台线程中执行。
Async.AwaitTask 文档中指出:“返回一个异步计算,等待给定的任务完成并返回其结果。”
这是否意味着当您调用返回任务的 C# 方法时,执行已经在后台开始了?如果是这样,那么将其包装在 Async 类型内还有什么意义呢?
我知道 F# 与 C# 异步模型的主要区别在于,F# 中只有在调用 Async.RunSynchronously 类似的方法时,异步执行才会开始。而在 C# 中,当一个方法返回一个任务(Task),执行通常(不总是)会立即开始在后台线程中执行。
Async.AwaitTask 文档中指出:“返回一个异步计算,等待给定的任务完成并返回其结果。”
这是否意味着当您调用返回任务的 C# 方法时,执行已经在后台开始了?如果是这样,那么将其包装在 Async 类型内还有什么意义呢?
async { ... }
块内使用let!
。在后一种情况下,封装的任务直到其封闭的async { ... }
块启动之前都不会启动。let printTask str =
async {
printfn "%s" str
} |> Async.StartAsTask
这没有做太多事情;它存在的唯一原因是为了让你知道它何时开始运行,因为它将向屏幕打印一条消息。如果你从F#交互调用它:
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”,然后返回已完成的任务。这证明任务立即启动。
但现在看看以下代码:
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的"异步 Web 下载器"示例,已经在内部使用Tasks而不是Async进行了调整。
这里,async { ... }
块包含三个Tasks,这些Task都被包装在Async.AwaitTask
中。(请注意,如果从任何这些代码行中删除 |> Async.AwaitTask
,您将收到类型错误)。对于每个Task,在它的代码行执行一次后,它会立即开始。但这是一个重要的点,因为总体的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 { ... }
块更容易地与返回Tasks的C#代码进行互操作。如果Async.AwaitTask调用在async { ... }
块内部,则任务直到启动封闭的Async
之前不会实际开始,因此您仍然可以获得“冷启动”Asyncs的所有优点。