注意:我不是一个专业的软件开发人员,但是我写了很多代码并没有使用异步的东西,所以如果这个问题真的很直接,我会道歉。
我正在与用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上有什么内容可以回答这些问题吗?如果是这样,那似乎并不明显。如果没有,你能指点我到哪儿吗?
答案 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
。这是一种特殊的野兽,其中总有一种当前的"一个(通过静态属性访问 - 谈论全局状态!),你可以要求它在相同的上下文中安排东西",其中"相同的上下文"由实现定义。
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
期望Task
,ConfigureAwait
返回ConfiguredTaskAwaitable
。