我在BackgroundWorker中有一个WebClient,但是由于某种原因,在启动对象之前创建对象时,它没有开始下载。 在主线程上工作正常。
像这样不起作用:
Dim AddRPB As New ProgressBar
Dim client As New WebClient
AddHandler client.DownloadProgressChanged, AddressOf DownloadingProgress
AddHandler client.DownloadDataCompleted, AddressOf DownloadComplete
client.DownloadDataAsync(New Uri(WebLink), Data)
像这样工作:
Dim client As New WebClient
AddHandler client.DownloadProgressChanged, AddressOf DownloadingProgress
AddHandler client.DownloadDataCompleted, AddressOf DownloadComplete
client.DownloadDataAsync(New Uri(WebLink), Data)
Dim AddRPB As New ProgressBar
Dim AddRPB As New ProgressBar
单行以某种方式打破了它,我不明白为什么。
答案 0 :(得分:1)
这可能并不完全准确,但这是我通过一些测试并在Reference Source的帮助下得出的:
ProgressBar
实例化之前/之后 WebClient
与SynchronizationContexts
一起使用,以便将数据发布回UI线程并调用其事件处理程序(BackgroundWorker
也是如此)。当您调用其Async
方法之一时,WebClient
立即创建一个绑定到调用线程的SynchronizationContext
的异步操作。如果上下文不存在,则会创建一个新上下文并将其绑定到该线程。
如果在RunWorkerAsync
事件处理程序中完成此操作而没有(或之前没有)创建ProgressBar
,则将为BackgroundWorker
的线程创建一个新的同步上下文。
到目前为止,一切都很好。一切仍然有效,但是事件处理程序将在后台线程而不是UI线程中执行。
ProgressBar
在开始下载之前使用ProgressBar
实例化代码,您现在正在非UI线程中创建控件,这将导致创建新SynchronizationContext
并将其绑定到该控件。后台线程以及控件本身。这个SynchronizationContext
有点不同,它是一个WindowsFormsSynchronizationContext
,它使用Control.Invoke()
和Control.BeginInvoke()
方法与他们认为是UI线程的方法进行通信。在内部,这些方法将一条消息发布到UI的消息泵,告诉它在UI线程上执行指定的方法。
这似乎是出问题的地方。通过在非UI线程中创建控件,从而在该线程中创建WindowsFormsSynchronizationContext
,WebClient
现在将在调用事件处理程序时使用该上下文。 WebClient
将调用WindowsFormsSynchronizationContext.Post()
,后者依次调用Control.BeginInvoke()
以在同步上下文的线程上执行该调用。唯一的问题是:该线程没有消息循环,无法处理BeginInvoke
消息。
无消息循环= BeginInvoke
消息将不被处理
消息将无法处理=没有任何东西调用指定的方法
该方法未调用=将永远不会引发WebClient
的{{1}}或DownloadProgressChanged
事件。
最后,所有这些再次归结为WinForms的黄金法则:
将所有与UI相关的工作留在UI线程上!
编辑:
如评论/聊天中所述,如果您所做的只是将进度条传递给
DownloadDataCompleted
的异步方法,则可以像这样解决它,并让WebClient
在UI线程,然后为您返回:Control.Invoke()