异步工作流 (F#)

本主题介绍 F# 中支持异步执行计算,而不会阻塞其他工作的执行。 例如,异步计算可用于编写一些应用程序,这些应用程序的 UI 在应用程序执行其他工作时仍可对用户进行响应。

async { expression }

备注

在之前的语法中,将由 expression 表示的计设置为异步运行,即在执行异步休眠操作、I/O 和其他异步操作时不中断当前计算线程。 当继续对当前线程执行操作时,异步计算通常会在后台线程上启动。 表达式的类型为 Async<'a>,其中 'a 是使用 return 关键字时由表达式返回的类型。 此类表达式中的代码被称作“异步块”。

可以通过多种方式进行异步编程,并且 Async 类提供了支持多个方案的方法。 常见的方法是,先创建表示需要异步运行的计算的 Async 对象,然后通过使用触发函数之一来开始这些计算。 各种触发函数提供了运行异步计算的不同方式,具体采用哪一种方式取决于您需要使用的是当前线程、后台线程还是 .NET Framework 任务对象,以及是否存在当计算完成时应运行的延续函数。 例如,若要在当前线程上开始异步计算,可以使用 Async.StartImmediate。 在从 UI 线程开始异步计算时,您不会阻塞处理用户操作(如键击和鼠标活动)的主事件循环,因此,应用程序仍可保持响应。

使用 let! 异步绑定

在异步工作流中,一些表达式和操作是同步的,而另一些则是旨在以异步方式返回结果的较长时间的计算。 以异步方式调用一个方法时,请使用 let!,而不是普通的 let 绑定。 let! 的作用是允许在执行计算的同时,继续执行其他计算或线程。 在 let! 绑定的右侧返回后,异步工作流的剩余部分将继续执行。

下面的代码演示了 let 和 let! 之间的差异。 使用 let 的代码行只是将异步计算创建为一个对象,稍后可以通过使用 Async.StartImmediate 或 Async.RunSynchronously 来运行该对象。 使用 let! 的代码行先开始计算,然后挂起线程,直到结果可用,执行过程才会继续。

// let just stores the result as an asynchronous operation.
let (result1 : Async<byte[]>) = stream.AsyncRead(bufferSize)
// let! completes the asynchronous operation and returns the data.
let! (result2 : byte[])  = stream.AsyncRead(bufferSize)

除了 let! 之外,您还可以使用 use! 执行异步绑定。 let! 和 use! 间的区别与 let 和 use 之间的区别相同。 对于use! 在结束当前的范围时释放对象。 请注意,在当前版本的 F# 语言中,use! 不允许将值初始化为 null,即使 use 允许也是如此。

异步基元

执行单个异步任务并返回相应结果的方法被称作“异步基元”,它们是专门设计用于 let! 的。 F# 核心库中定义了若干个异步基元。 模块 Microsoft.FSharp.Control.WebExtensions 中定义了用于 Web 应用程序的两个此类方法:WebRequest.AsyncGetResponseWebClient.AsyncDownloadString。 这两个基元在给定 URL 的情况下可从网页中下载数据。 AsyncGetResponse 生成一个 WebResponse 对象,而 AsyncDownloadString 生成一个表示网页的 HTML 的字符串。

Microsoft.FSharp.Control.CommonExtensions 模块中包含了用于异步 I/O 操作的多个基元。 Stream 类的这些扩展方法为 Stream.AsyncReadStream.AsyncWrite

F# PowerPack 中提供了其他异步基元。 此外,也可以通过定义其整个主体括在异步块中的函数来编写自己的异步基元。

若要将 .NET Framework 中为其他异步模型设计的异步方法用于 F# 异步编程模型,则可以创建返回 F# Async 对象的函数。 F# 库中包含一些函数,利用这些函数可轻松做到这一点。

此处包括使用异步工作流的示例;文档中还有其他很多 异步类 方法的示例。

示例

本示例说明如何使用异步工作流来并行执行计算。

在下面的代码示例中,函数 fetchAsync 将获取从 Web 请求返回的 HTML 文本。 fetchAsync 函数包含异步代码块。 在对异步基元的结果(此示例中为 AsyncDownloadString)执行绑定时,将使用 let!,而不是使用 let。

可使用 Async.RunSynchronously 函数执行异步操作并等待其结果。 例如,可以通过将 Async.Parallel 函数与 Async.RunSynchronously 函数一起使用,并行执行多个异步操作。 Async.Parallel 函数采用 Async 对象的列表作为参数,并为要并行运行的每个 Async 任务对象设置代码,然后返回表示并行计算的 Async 对象。 正如对待单个操作一样,调用 Async.RunSynchronously 可开始执行。

runAll 函数并行启动三个异步工作流,并一直等到它们全部完成。

open System.Net
open Microsoft.FSharp.Control.WebExtensions

let urlList = [ "Microsoft.com", "https://www.microsoft.com/"
                "MSDN", "https://msdn.microsoft.com/"
                "Bing", "https://www.bing.com"
              ]

let fetchAsync(name, url:string) =
    async { 
        try
            let uri = new System.Uri(url)
            let webClient = new WebClient()
            let! html = webClient.AsyncDownloadString(uri)
            printfn "Read %d characters for %s" html.Length name
        with
            | ex -> printfn "%s" (ex.Message);
    }

let runAll() =
    urlList
    |> Seq.map fetchAsync
    |> Async.Parallel 
    |> Async.RunSynchronously
    |> ignore

runAll()

请参见

参考

Control.Async 类 (F#)

其他资源

F# 语言参考

计算表达式 (F#)