为什么我的第二段F#异步代码可以正常工作,但第一部分不起作用?

时间:2018-01-25 17:50:58

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

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

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

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

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

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

所以我搞砸了代码,并尝试了以下方法,这有效。

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

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

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

1 个答案:

答案 0 :(得分: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

当您运行res2时,您将获得两行输出,其中包含两个不同的数字。 async内部运行的线程与res2本身运行的线程不同。潜入async会让你处于另一个角色。

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

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

但是,当然,由于您使用Async.RunSynchronously阻止事件循环的线程,因此任务等待永远不会发生。您正在等待它发生,它正在等待您释放该线程。阿卡"僵局"。

要解决这个问题,你需要在另一个线程上开始等待,以便WinForms'没有使用同步上下文 - 一个你不小心偶然发现的解决方案。

另一种推荐的解决方案是明确告诉TPL不要使用" current"上下文来自Task.ConfigureAwait

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

不幸的是,这不会编译,因为Async.AwaitTask期望TaskConfigureAwait返回ConfiguredTaskAwaitable