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

17

我知道 F# 与 C# 异步模型的主要区别在于,F# 中只有在调用 Async.RunSynchronously 类似的方法时,异步执行才会开始。而在 C# 中,当一个方法返回一个任务(Task),执行通常(不总是)会立即开始在后台线程中执行。

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

这是否意味着当您调用返回任务的 C# 方法时,执行已经在后台开始了?如果是这样,那么将其包装在 Async 类型内还有什么意义呢?


在C#中,当一个方法返回一个任务时,执行已经在后台线程中开始了。- 不一定。它取决于任务是如何创建的,它是否会自动启动。 - Enigmativity
@Enigmativity 你是正确的。我已经更新了。我是在一个C#方法中使用Task.Run(某些操作)的上下文中提出这个问题。 - user3587180
“将其包装在异步类型中”是什么意思? - Enigmativity
也许我在这里没有使用正确的术语。假设getCustomerAsync返回Task<Customer>。如果我调用Async.AwaitTask,它将返回Async<Customer>。我不明白为什么它会返回Async<Customer>,如果任务已经在后台开始了。 - user3587180
1个回答

25
将任务包装在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的所有优点。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接