为什么`Async.SwitchToContext`不返回?

时间:2018-11-26 20:24:51

标签: f#

我正在尝试抓取一些需要运行JavaScript的网站,以便该文档拥有我感兴趣的所有数据。我正在尝试打开WebBrowser并等待文档加载,但是当我尝试切换回WebBrowser处于打开状态的线程时,无法获得数据。尝试在不切换回线程的情况下运行它会产生转换错误。 =(

是什么阻止async切换线程?我该如何解决这个问题?

脚本

open System
open System.Windows.Forms
open System.Threading

let step a = do printfn "%A" a

let downloadWebSite (address : Uri) (cont : HtmlDocument -> 'a) =
    let browser = new WebBrowser()
    let ctx = SynchronizationContext.Current
    browser.DocumentCompleted.Add (fun _ ->
        printfn "Document Loaded" )

    async {
        do step 1
        do browser.Navigate(address)
        do step 2
        let! _ = Async.AwaitEvent browser.DocumentCompleted
        do step 3
        do! Async.SwitchToContext ctx
        do step 4
        return cont browser.Document }

let test = 
    downloadWebSite (Uri "http://www.google.com") Some
    |> Async.RunSynchronously

输出

> 
1
2
Document Loaded
3
# It just hangs here. I have to manually interrupt fsi.
- Interrupt
>
4

1 个答案:

答案 0 :(得分:0)

您的方法存在的问题是RunSynchronously阻塞了您试图使用Async.SwitchToContext ctx运行其余异步计算的线程。

使用F#Interactive时,有一个主线程在F#Interactive中运行并处理用户交互。这是可以使用Windows窗体控件的线程,因此您可以在WebBrowser之外正确创建async。等待DocumentCompleted发生在线程池线程(运行异步工作流)上,但是当您尝试切换回主线程时,它已被Async.RunSynchronously阻塞。

您可以通过运行一个调用Application.DoEvents的循环来处理主线程上的事件来避免阻塞线程(这也将使其运行异步的其余部分)。您的downloadWebSite保持不变,但现在您等待使用:

let test = 
    downloadWebSite (Uri "http://www.google.com") Some
    |> Async.Ignore
    |> Async.StartAsTask

while not test.IsCompleted do
  System.Threading.Thread.Sleep(100)
  System.Windows.Forms.Application.DoEvents()

这有点hack-如果您真的不需要等待结果(例如,只需返回一个任务并在运行下一个命令之前等待),则可能会有更好的结构化方式。做到这一点。