asynchronous - 如何在 F# 中使用异步从 WebBrowser 返回 HtmlDocument?
问题描述
我试图在加载完成之前抓取一系列在 DOM 上运行大量 javascript 的网站。这意味着我使用的是 aWebBrowser
而不是友好的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
?
解决方案
问题出在这一行:
let browserContext = SynchronizationContext()
您手动创建了一个新实例,SynchronizationContext
但没有将其与 UI 线程或任何线程相关联。这就是当您访问browser.Document
必须在 UI 线程上访问的程序时程序崩溃的原因。
要解决这个问题,只需使用SynchronizationContext
已经与 UI 线程关联的现有:
let browserContext = SynchronizationContext.Current
我假设该downloadWebSite
函数是在 UI 线程上调用的。如果不是,您可以将上下文从某处传递到函数中,或使用全局变量。
更好的设计
尽管Async.SwitchToContext
您可以确保下一行在 UI 线程中访问并返回文档,但接收文档的客户端代码可能在非 UI 线程上运行。更好的设计是使用延续函数。您可以返回由作为参数SomeType
传入的延续函数产生的值,而不是直接返回文档。downloadWebSite
通过这种方式,可以确保继续函数在 UI 线程上运行:
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
推荐阅读
- hibernate - 多对多映射显示不正确的结果
- sql-server - 用于 Kakfa Connect 的 Debezium SQLServerConnector 不在 Kafka 中创建主题
- php - foreach 循环仅在循环中输出第一个值
- angular - 错误类型错误:“_co.voiture 未定义”如何修复此错误
- mysql - 选择具有不同日期间隔的相同列
- sql - 如何从与外部查询连接的 COUNT 子查询中设置列值?
- python - 熊猫查找日期频率
- mockito - 无法检查在 Mockito 中是否调用了间谍方法
- javascript - 如何将我的应用重定向到异步登录页面
- jquery - 如何从 Datepicker 类型的 var 中获取“getValue”值