本文介绍异步表达式的 F# 支持。 异步表达式提供异步执行计算的一种方法,即不阻止执行其他工作。 例如,异步计算可用于编写具有在应用程序执行其他工作时对用户保持响应的 UI 的应用。 F# 异步工作流编程模型允许你在隐藏库中线程转换的详细信息时编写功能程序。
还可以使用 任务表达式创作异步代码,从而直接创建 .NET 任务。 与创建或使用 .NET 任务的 .NET 库进行广泛互作时,首选使用任务表达式。 在 F# 中编写大多数异步代码时,首选 F# 异步表达式,因为它们更简洁、更组合,并避免与 .NET 任务关联的某些注意事项。
语法
async { expression }
注解
在前面的语法中,所 expression
表示的计算设置为异步运行,也就是说,在执行异步睡眠作、I/O 和其他异步作时,不会阻止当前计算线程。 异步计算通常在后台线程上启动,同时执行在当前线程上继续。 表达式的类型是 Async<'T>
,其中 'T
是使用 return
关键字时表达式返回的类型。
该 Async
类提供支持多种方案的方法。 一般方法是创建 Async
表示要异步运行的计算或计算的对象,然后使用触发函数之一启动这些计算。 使用的触发器取决于是要使用当前线程、后台线程还是 .NET 任务对象。 例如,若要在当前线程上启动异步计算,可以使用 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!
只能用于直接等待 F# 异步计算 Async<T>
。 可以间接等待其他类型的异步作:
- 结合 .NET 任务和非泛型Task任务 Task<TResult>
Async.AwaitTask
- .NET 值任务ValueTask<TResult>和非泛型ValueTask,结合和
.AsTask()
Async.AwaitTask
- 结合
task { return! expr } |> Async.AwaitTask
F# RFC FS-1097 中指定的“GetAwaiter”模式的任何对象。
控制流
异步表达式可以包括控制流构造,例如for .. in .. do
、while .. do
、try .. with ..
、try .. finally ..
和if .. then .. else
if .. then ..
。 它们反过来可能包括进一步的异步构造,但同步执行的和finally
处理程序除外with
。
F# 异步表达式不支持异步 try .. finally ..
。 对于这种情况,可以使用 任务表达式 。
use
和 use!
绑定
在异步表达式中, use
绑定可以绑定到类型的 IDisposable值。 对于后者,处置清理操作将异步执行。
除了 let!
,你还可以使用 use!
来执行异步绑定。
let!
和use!
之间的差异与let
和use
之间的差异相同。
use!
对象在当前范围结束时被销毁。 请注意,在 F# 的当前版本中, use!
不允许将值初始化为 null,即使 use
如此。
异步基元
执行单个异步任务并返回结果的方法称为 异步基元,这些方法专为使用 let!
而设计。 F# 核心库中定义了多个异步基元。 模块中 FSharp.Control.WebExtensions
定义了两种 Web 应用程序的此类方法: WebRequest.AsyncGetResponse
( HttpClient.GetStringAsync 包装为 Async.AwaitTask
与 F# 的异步模型兼容)。 这两个基元都从网页下载数据,给定 URL。
AsyncGetResponse
生成一个 System.Net.WebResponse
对象,并 GetStringAsync
生成一个表示网页 HTML 的字符串。
模块中包含多个用于异步 I/O作的 FSharp.Control.CommonExtensions
基元。 类的 System.IO.Stream
这些扩展方法是 Stream.AsyncRead
和 Stream.AsyncWrite
。
还可以通过定义主体是异步表达式的函数或方法来编写自己的异步基元。
若要在 .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
open System.Net.Http
let urlList = [ "Microsoft.com", "http://www.microsoft.com/"
"MSDN", "http://msdn.microsoft.com/"
"Bing", "http://www.bing.com"
]
let fetchAsync(name, url:string) =
async {
try
let uri = new System.Uri(url)
let httpClient = new HttpClient()
let! html = httpClient.GetStringAsync(uri) |> Async.AwaitTask
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()