为什么我的第二段F#异步代码工作,但第一部分没有?

Che*_*vas 10 f# multithreading task-parallel-library async-await

注意:我不是一个专业的软件开发人员,但是我写了很多不使用异步的代码,所以如果这个问题真的很直接,我很抱歉.

我正在与用C#编写的库连接.有一个特殊的函数(让我们称之为'func')返回'Threading.Tasks.Task>'

我正在使用func在F#中构建一个库.我在控制台应用程序中测试了以下代码,它工作正常.

let result = 
        func()
        |> Async.AwaitTask
        |> Async.RunSynchronously
        |> Array.ofSeq
Run Code Online (Sandbox Code Playgroud)

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

所以我搞乱了代码,并尝试了以下,这是有效的.

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

            return temp
        } |> Async.RunSynchronously |> Array.ofSeq
Run Code Online (Sandbox Code Playgroud)

为什么第一个片段不起作用?为什么第二个片段有效?这个页面上有什么内容可以回答这些问题吗?如果是这样,那似乎并不明显.如果没有,你能指点我到哪儿吗?

Fyo*_*kin 8

第一个和第二个代码段之间的区别在于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
Run Code Online (Sandbox Code Playgroud)

当你跑步时res2,你会得到两行输出,上面有两个不同的数字.async运行内部的线程与res2自身运行的线程不同.潜入一个async让你在另一个线程.

现在,这与.NET TPL任务的实际工作方式相互作用.当你去等待任务时,你不只是在一些随机线程上得到一个回调,哦不!而是通过"当前"调度您的回调SynchronizationContext.这是一种特殊的野兽,其中总有一个"当前"的野兽(可以通过静态属性访问 - 谈论全球状态!),你可以要求它安排"在同一个环境中"的东西,其中的概念是"相同的上下文"由实现定义.

当然,WinForms有自己的实现,恰如其分WindowsFormsSynchronizationContext.当你在WinForms事件处理程序中运行,并且要求当前上下文安排某些事情时,它将使用WinForms自己的事件循环来调度 - 一个la Control.Invoke.

但是当然,既然你用你的方式阻止了事件循环的线程Async.RunSynchronously,那么任务就永远不会有机会发生.你正在等待它发生,它正在等你释放线程.阿卡"僵局".

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

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

let result = 
    func().ConfigureAwait( continueOnCapturedContext = false )
    |> Async.AwaitTask
    |> Async.RunSynchronously
    |> Array.ofSeq
Run Code Online (Sandbox Code Playgroud)

不幸的是,这不会编译,因为Async.AwaitTask期望a Task,并ConfigureAwait返回一个ConfiguredTaskAwaitable.