在不创建新线程的情况下将异步计算转换为 .NET 任务

Convert async computation to .NET task without creating a new thread

我正在通过构建 ASP.NET 核心应用程序来学习 F#。我需要实现一个接口Microsoft.AspNetCore.Identity.IUserStore<'TUser>;此接口具有 return Task 等方法:

member this.CreateAsync (user : User, cancellationToken : CancellationToken) =
    async {
        let connectionString = configuration.GetConnectionString("SocialScoreApp")
        let! rowCountResult = user |> createAsync connectionString
        match rowCountResult with
        | Ok _ -> return IdentityResult.Success
        | Error e ->
        let identityError = new IdentityError()
        identityError.Description <- e
        return IdentityResult.Failed(identityError)
    } |> Async.StartAsTask

但是,根据文档,Async.StartAsTask“在线程池上执行计算”。但这是一个 I/O 绑定操作,所以我不想涉及任何新线程。有什么办法可以实现吗?

编辑:这是createAsync代码供参考:

let createAsync (connectionString : string) (user:User) =
    let sql = "..."
    let args = dict [
        // ...
    ]
        
    async {
        use connection = new SqlConnection(connectionString)
        do! connection.OpenAsync() |> Async.AwaitTask

        try
            let! rowCount = connection.ExecuteAsync(sql, args) |> Async.AwaitTask
            return Ok rowCount
        with
        | e -> return Error e.Message
    }

如果您查看其工作原理,StartAsTask (source) operation creates a TaskCompletionSource, which creates a task that is returned to the caller. This is later used to report the result of the computation. In the meantime, the computation is started using QueueAsync, which invokes QueueWorkItemWithTrampoline (source) 并且它使用标准 .NET ThreadPool.QueueUserWorkItem.

这意味着当你运行StartAsTask时,异步工作流被添加到线程池中。但是,IO 绑定操作的关键是您在代码段中使用 let!

let connectionString = configuration.GetConnectionString("SocialScoreApp")
let! rowCountResult = user |> createAsync connectionString

添加到线程池的工作项只会运行GetConnectionString。然后它会调用 createAsync,其中可能包含一些非阻塞 IO 操作。一旦评估达到这一点,线程池中的工作就会完成,它会安排在 IO 操作完成时调用回调 - 稍后调用回调并将其余计算作为新工作添加到线程池中物品。因此,您不会在 IO 操作 运行ning 时阻塞线程池。