【问题标题】:Why does my second snippet of F# async code work, but the first does not?为什么我的第二个 F# 异步代码片段可以工作,但第一个不能?
【发布时间】:2018-07-05 01:36:49
【问题描述】:

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

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

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

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

但是,当我从 WinForms 应用程序(这最终是我想要做的)运行它时,执行会阻塞在表单代码中并且永远不会返回。

所以我弄乱了代码并尝试了以下方法。

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

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

为什么第一个 sn-p 不起作用?为什么第二个 sn-p 工作? this page 上有什么可以回答这些问题的吗?如果是这样,它似乎并不明显。如果没有,你能指点我一个可以做的地方吗?

【问题讨论】:

    标签: multithreading f# async-await task-parallel-library


    【解决方案1】:

    您的第一个和第二个 sn-ps 之间的区别在于 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 任务的实际工作方式交互。当你去等待一个任务时,你不只是在某个随机线程上得到一个回调,哦不!相反,您的回调是通过“当前”SynchronizationContext 安排的。那是一种特殊的野兽,其中总是有一个“当前”的(可通过静态属性访问 - 谈论全局状态!),您可以要求它“在相同的上下文中”安排东西,其中的概念“相同的上下文”由实现定义。

    WinForms 当然有它自己的实现,恰当地命名为WindowsFormsSynchronizationContext。当您在 WinForms 事件处理程序中运行,并且您要求当前上下文安排某些事情时,它将使用 WinForms 自己的事件循环进行安排 - 例如 Control.Invoke

    当然,由于您使用Async.RunSynchronously 阻塞了事件循环的线程,因此等待任务永远没有机会发生。您正在等待它发生,它正在等待您释放线程。又名“死锁”。

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

    另一种推荐的解决方案是通过Task.ConfigureAwait 明确告诉 TPL 不要使用“当前”上下文:

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

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

    【讨论】:

    • 很好的解释。但我认为第一个 sn-p 与控制台应用程序一起工作的原因并不十分清楚。是不是因为控制台应用程序以不同的方式实现了 SynchronizationContext,从而不会发生这种特定的死锁?
    • 是的,在没有 Windows 窗体的情况下,使用“默认”同步上下文,它只是在线程池上安排操作。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-29
    • 1970-01-01
    • 2017-05-19
    • 2021-10-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多