【问题标题】:How does Async.AwaitTask work in f#?Async.AwaitTask 在 f# 中是如何工作的?
【发布时间】:2018-07-05 14:23:52
【问题描述】:

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

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

这是否意味着,当您调用返回任务的 c# 方法时,已经在后台开始执行?如果是这样,将其包装在 Async 类型中的意义何在?

【问题讨论】:

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

标签: f#


【解决方案1】:

将 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 中。 (请注意,如果您从这些行中删除了|&gt; Async.AwaitTask,则会收到类型错误)。对于每个任务,一旦其代码行执行,它将立即启动。但这是很重要的一点,因为整个async { ... } 计算不是立即开始的。所以我可以这样做:

let a = fetchUrlAsync "http://www.google.com"

在 F# Interactive 中打印的唯一内容是 val a : Async&lt;unit&gt;。我想等多久就等多久,不会打印任何其他内容。只有当我真正开始 a 它才会开始运行:

a |> Async.RunSynchronously

这会立即打印started downloading http://www.google.com,然后在短暂的暂停后打印finished downloading http://www.google.com

这就是Async.AwaitTask 的目的:允许async { ... } 块更轻松地与返回任务的C# 代码进行互操作。如果 Async.AwaitTask 调用在 async { ... } 块内,那么在封闭的 Async 启动之前,任务实际上不会启动,因此您仍然可以获得“冷启动”异步的所有优势。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-07-12
    • 1970-01-01
    • 2011-02-23
    • 2021-09-25
    • 2014-07-15
    • 2018-02-08
    • 1970-01-01
    相关资源
    最近更新 更多