Почему `Async.SwitchToContext` не вернется? - PullRequest
0 голосов
/ 26 ноября 2018

Я пытаюсь очистить некоторые веб-сайты, на которых необходимо запустить 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 голосов
/ 27 ноября 2018

Проблема с вашим подходом состоит в том, что RunSynchronously блокирует поток, который вы пытаетесь использовать для запуска остальной части асинхронного вычисления, используя Async.SwitchToContext ctx.

При использовании F # Interactive, есть одна главнаяПоток, который работает в F # Interactive и обрабатывает взаимодействия с пользователем.Это поток, который может использовать элементы управления Windows Forms, поэтому вы правильно создаете 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()

Это немного хак - и, возможно, есть лучший способ структурировать это, если вам действительно не нужно ждатьрезультат (например, просто вернуть задачу и подождать перед выполнением следующей команды), но это должно сработать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...