为什么我的 F# 异步代码的第二个片段有效,而第一个片段却无效?

Why does my second snippet of F# async code work, but the first does not?

注意:我不是专业的软件开发人员,但我确实编写了很多不使用异步的代码,所以如果这个问题真的很直白,我深表歉意。

我正在与用 C# 编写的库进行交互。有一个特定的函数(让我们称之为 'func') returns a 'Threading.Tasks.Task>'

我正在用 F# 构建一个使用 func 的库。我在控制台应用程序中测试了以下代码,它运行良好。

let result = 
        func()
        |> Async.AwaitTask
        |> Async.RunSynchronously
        |> Array.ofSeq

然而,当我从 WinForms 应用程序 运行 它时(这最终是我想要做的),执行块在表单代码中,从不 returns。

所以我弄乱了代码并尝试了以下方法,它起作用了。

let result = 
        async{
            let! temp = 
                func()
                |> Async.AwaitTask

            return temp
        } |> Async.RunSynchronously |> Array.ofSeq

为什么第一个代码段不起作用?为什么第二个片段有效? this page 上是否有任何内容可以回答这两个问题?如果是这样,这似乎并不明显。如果没有,你能告诉我哪里有吗?

您的第一个和第二个片段之间的区别在于 AwaitTask 调用发生在 不同的线程上

试试这个来验证:

let printThread() = printfn "%d" System.Threading.Thread.CurrentThread.ManagedThreadId

let result = 
    printThread()
    func()
    |> Async.AwaitTask
    |> Async.RunSynchronously
    |> Array.ofSeq

let res2 = 
    printThread()
    async {
        printThread()
        let! temp = func() |> Async.AwaitTask
        return temp
    } |> Async.RunSynchronously |> Array.ofSeq

当您 运行 res2 时,您将得到两行输出,上面有两个不同的数字。 async 运行 内部所在的线程与 res2 本身 运行 所在的线程不同。深入 async 会使您处于不同的线程。

现在,这与 .NET TPL 任务的实际工作方式相互作用。当你去等待一个任务时,你不只是在一些随机线程上得到回调,哦不!相反,您的回调是通过 "current" SynchronizationContext 安排的。这是一种特殊的野兽,总有一个 "current" 一个(可通过静态访问 属性 - 谈论全局状态!),你可以要求它安排事情 "in the same context" ,其中 "same context" 的概念由实现定义。

WinForms 当然有自己的实现,恰如其分地命名为 WindowsFormsSynchronizationContext. When you're running within a WinForms event handler, and you ask the current context to schedule something, it will be scheduled using the WinForms' own event loop - a la Control.Invoke

但是当然,由于您用 Async.RunSynchronously 阻塞了事件循环的线程,等待任务永远不会发生。你在等它发生,它也在等你释放线程。又名 "deadlock".

要解决此问题,您需要在不同的线程上启动等待,以便不使用 WinForms 的同步上下文 - 您不小心偶然发现的解决方案。

另一种推荐的解决方案是通过 Task.ConfigureAwait:

明确告诉 TPL 不要使用 "current" 上下文
let result = 
    func().ConfigureAwait( continueOnCapturedContext = false )
    |> Async.AwaitTask
    |> Async.RunSynchronously
    |> Array.ofSeq

不幸的是,这不会编译,因为 Async.AwaitTask 需要 Task,而 ConfigureAwait returns 需要 ConfiguredTaskAwaitable.