如何在 F# 中简化异步编程

How to Simplify Asynchronous Programming in F#

我有 C# 背景,使用过 async/await。我正在尝试使用库找到一种“不那么冗长”的编程方式。 (特别是用于浏览器自动化的 Microsoft Playwright 库)

let (~~) = Async.AwaitTask
let getLastPageNumber(page: IPage) = 
    let playwright = ~~Playwright.CreateAsync() |> Async.RunSynchronously
    let browser = ~~playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions(Headless = false )) |> Async.RunSynchronously
    ~~page.GotoAsync("https://go.xero.com/BankRec/BankRec.aspx?accountID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&page1") |> Async.RunSynchronously |> ignore
    let lastPageLink = ~~page.QuerySelectorAsync("#mainPagerEnd") |> Async.RunSynchronously
    if lastPageLink = null then
        //this is the last page
        1
    else
        let lastPageNumber = ~~lastPageLink.GetAttributeAsync("href") |> Async.RunSynchronously
        lastPageNumber |> int

我使用 Async.AwaitTask 的别名 ~~ 缩短了一些内容,但似乎需要很多代码才能完成一些在 C# 中容易得多的事情。

Async.RunSynchronously 只能作为最后的手段使用,因为它会阻塞一个线程来执行计算,这违背了使用 async/tasks.

的目的

F# 等同于 C# 的 async/await 是使用 F# 的 Async 类型和 async 计算表达式。但是,如果您使用的是使用 .NET Task 类型的 .NET 库,那么您可以使用具有 task 计算表达式的 TaskBuilder.fs 库。

那么你可以这样写函数:

open FSharp.Control.Tasks

let getLastPageNumber(page: IPage) =  task {
    let! playwright = Playwright.CreateAsync()
    let! browser = playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions(Headless = false ))
    let! _ = page.GotoAsync("https://go.xero.com/BankRec/BankRec.aspx?accountID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&page1")
    let! lastPageLink = page.QuerySelectorAsync("#mainPagerEnd")
    if lastPageLink = null then
        //this is the last page
        return 1
    else
        let! lastPageNumber = lastPageLink.GetAttributeAsync("href")
        return lastPageNumber |> int
}

在计算表达式(也称为构建器)中let!用于等待任务。

请注意,此函数现在 returns Task<int> 而不是 int,因此调用者可能需要遵循类似的模式并进一步传播任务。

您可以阅读有关在 F# here 中使用 async 的更多信息。其中一些知识也可以应用于 task 计算表达式。