Async.AwaitTask如何在f#中工作?

时间:2018-07-05 01:29:15

标签: f#

我知道f#和c#异步模型之间的主要区别在于,在f#中,除非您调用Async.RunSynchronously之类的代码,否则异步执行不会开始。在c#中,当方法返回任务时,通常(并非总是)立即在后台线程中开始执行。

Async.AwaitTask文档说:“返回等待给定任务完成并返回其结果的异步计算。”

这是否意味着,当您调用返回任务的c#方法时,执行已经在后台启动了?如果是这样,将其包装在异步类型中又有什么意义呢?

1 个答案:

答案 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的所有优点