【问题标题】:Convert async computation to .NET task without creating a new thread将异步计算转换为 .NET 任务而不创建新线程
【发布时间】:2021-06-25 17:45:08
【问题描述】:

我正在通过构建 ASP.NET Core 应用程序来学习 F#。我需要实现一个接口Microsoft.AspNetCore.Identity.IUserStore<'TUser>;此接口具有返回 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
    }

【问题讨论】:

  • C# 是从哪里来的?
  • 对不起,我想把 .NET。会修复的。
  • 任务不是线程。它们本质上与异步计算相同 - promise 某事将在未来完成。您已经在异步计算中使用了任务。异步 ADO.NET 方法返回一个任务
  • 我知道任务不是线程。但是Async.StartAsTask 似乎确实根据我对文档的解释在另一个线程上开始了计算。如果我删除 |&gt; Async.StartAsTask 我会收到错误,因为返回类型是 Async&lt;IdentityResult&gt; 而不是 Task&lt;IdentityResult&gt;
  • AsyncTask 实例在运行时间方面有所不同:Async 按需运行,但 Task 立即运行。因此,如果没有困扰您的“I/O 效应”,就不可能创建Task

标签: .net asynchronous async-await f# task


【解决方案1】:

如果你看看事情是如何工作的,StartAsTask (source) 操作会创建一个TaskCompletionSource,它会创建一个返回给调用者的任务。这稍后用于报告计算结果。同时,使用QueueAsync 开始计算,它调用QueueWorkItemWithTrampoline (source),这使用标准.NET ThreadPool.QueueUserWorkItem

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

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

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

【讨论】:

  • 谢谢托马斯,一如既往!添加了createAsync代码供参考。
猜你喜欢
  • 1970-01-01
  • 2014-03-29
  • 2020-01-28
  • 2014-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-28
  • 1970-01-01
相关资源
最近更新 更多