我知道f#和c#异步模型之间的主要区别在于,在f#中,除非您调用Async.RunSynchronously之类的代码,否则异步执行不会开始。在c#中,当方法返回任务时,通常(并非总是)立即在后台线程中开始执行。
Async.AwaitTask文档说:“返回等待给定任务完成并返回其结果的异步计算。”
这是否意味着,当您调用返回任务的c#方法时,执行已经在后台启动了?如果是这样,将其包装在异步类型中又有什么意义呢?
答案 0 :(得分:14)
将Task封装在Async中的目的是更轻松地将其与其他async组合在一起,或者将其与let!
块中的async { ... }
一起使用。而在后一种情况下,包装的Task除非启动其封闭的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”,然后返回完成的任务。这证明任务已立即启动。
但是现在看下面的代码:
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,适用于内部使用Tasks而非Asyncs。)
这里,async { ... }
块包含三个任务,所有任务都包装在Async.AwaitTask
中。 (请注意,如果您从任何这些行中删除了|> Async.AwaitTask
,都会出现类型错误)。对于每个任务,一旦执行其代码行,它将立即开始。但这很重要,因为整个async { ... }
计算不是不是立即开始的。所以我可以做:
let a = fetchUrlAsync "http://www.google.com"
F#Interactive中唯一打印的是val a : Async<unit>
。只要我愿意,我就可以等待,并且没有其他打印内容。实际上,只有当我 start 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
启动之后,Task才会真正启动,因此您仍然可以获得“冷启动” Async的所有优点