Как вернуть HtmlDocument из WebBrowser с помощью асинхронного в F #? - PullRequest
0 голосов
/ 17 ноября 2018

Я пытаюсь очистить серию веб-сайтов, которые запускают множество Javascript на DOM, прежде чем загрузка будет завершена.Это означает, что я использую WebBrowser вместо более дружественного WebClient.Проблема, которую я хотел бы решить, состоит в том, чтобы дождаться срабатывания события WebBrowser.DocumentCompleted и затем вернуть WebBrowser.Document.Затем я выполняю некоторую постобработку на HtmlDocument, но пока не могу его вернуть.

Код, который у меня есть

let downloadWebSite (address : string) = 
    let browser = new WebBrowser()
    let browserContext = SynchronizationContext()
    browser.DocumentCompleted.Add (fun _ ->
        printfn "Document Loaded")

    async {
        do browser.Navigate(address)
        let! a = Async.AwaitEvent browser.DocumentCompleted
        do! Async.SwitchToContext(browserContext)
        return browser.Document)
    }


[downloadWebSite "https://www.google.com"]
|> Async.Parallel // there will be more addresses when working
|> Async.RunSynchronously

Ошибка

System.InvalidCastException: Specified cast is not valid.
   at System.Windows.Forms.UnsafeNativeMethods.IHTMLDocument2.GetLocation()
   at System.Windows.Forms.WebBrowser.get_Document()
   at FSI_0058.downloadWebSite@209-41.Invoke(Unit _arg2) in C:\Temp\Untitled-1.fsx:line 209
   at Microsoft.FSharp.Control.AsyncPrimitives.CallThenInvokeNoHijackCheck[a,b](AsyncActivation`1 ctxt, FSharpFunc`2 userCode, b result1)
   at Microsoft.FSharp.Control.Trampoline.Execute(FSharpFunc`2 firstAction)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Microsoft.FSharp.Control.AsyncResult`1.Commit()
   at Microsoft.FSharp.Control.AsyncPrimitives.RunSynchronouslyInAnotherThread[a](CancellationToken token, FSharpAsync`1 computation, FSharpOption`1 timeout)
   at Microsoft.FSharp.Control.AsyncPrimitives.RunSynchronously[T](CancellationToken cancellationToken, FSharpAsync`1 computation, FSharpOption`1 timeout)
   at Microsoft.FSharp.Control.FSharpAsync.RunSynchronously[T](FSharpAsync`1 computation, FSharpOption`1 timeout, FSharpOption`1 cancellationToken)
   at <StartupCode$FSI_0058>.$FSI_0058.main@()
Stopped due to error

То, что я думаю, происходит

Есть несколько проблем, которые заставляют меня верить, чтоЯ получаю доступ к WebBrowser из другого потока. 1 2 3

Запрошенная помощь

  • Правильно ли здесь использование Async.SwitchToContext(browserContext)?
  • Можно ли упростить общий подход?
  • Есть ли концепция, о которой я не знаю?
  • Как мне получить WebBrowser.Document?

1 Ответ

0 голосов
/ 18 ноября 2018

Проблема в этой строке:

let browserContext = SynchronizationContext()

Вы вручную создали новый экземпляр SynchronizationContext, но не связали его с потоком пользовательского интерфейса или каким-либо другим потоком. Вот почему программа аварийно завершает работу при доступе к browser.Document, который должен быть доступен в потоке пользовательского интерфейса.

Чтобы решить эту проблему, просто используйте существующий SynchronizationContext, который уже был связан с потоком пользовательского интерфейса:

let browserContext = SynchronizationContext.Current

Я предположил, что функция downloadWebSite вызывается в потоке пользовательского интерфейса. Если это не так, вы можете передать контекст откуда-то в функцию или использовать глобальную переменную.

Лучший дизайн

Хотя с Async.SwitchToContext вы можете убедиться, что следующая строка получает доступ и возвращает документ в потоке пользовательского интерфейса, но код клиента, который получает документ, может выполняться в потоке, не являющемся пользовательским интерфейсом. Лучшим вариантом является использование функции продолжения . Вместо прямого возврата документа вы можете вернуть значение SomeType, созданное функцией продолжения, переданной в downloadWebSite в качестве параметра. Таким образом, функция продолжения гарантированно будет запущена в потоке пользовательского интерфейса:

let downloadWebSite (address : string) cont =
    let browser = new WebBrowser()
    let browserContext = SynchronizationContext.Current
    browser.DocumentCompleted.Add (fun _ ->
        printfn "Document Loaded")

    async {
        do browser.Navigate(address)
        let! a = Async.AwaitEvent browser.DocumentCompleted
        do! Async.SwitchToContext(browserContext)
        // the cont function is ensured to be run on UI thread:
        return cont browser.Document }

[downloadWebSite "https://www.google.com" (fun document -> (*safely access document*))]
|> Async.Parallel
|> Async.RunSynchronously
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...