创建对象时 Webclient 下载未启动

Webclient Download not starting when creating object

我在 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

那一行莫名其妙地打断了它,我不明白为什么。

这可能不完全准确,但这是我在 Reference Source:

的帮助下通过一些测试得出的结论

Without/before实例化ProgressBar

WebClientSynchronizationContexts 一起工作,以便 post 数据返回到 UI 线程并调用其事件处理程序(BackgroundWorker ).当您调用其中一个 Async 方法时,WebClient 会立即创建一个异步操作,该操作绑定到调用线程的 SynchronizationContext。如果上下文不存在,则会创建一个新上下文并将其绑定到该线程。

如果在 RunWorkerAsync 事件处理程序中完成此操作而没有(或之前)创建 ProgressBar,将为 BackgroundWorker 的线程创建一个新的同步上下文。

到目前为止一切顺利。一切仍然有效,但事件处理程序将在后台线程而不是 UI 线程中执行。

在开始下载之前创建ProgressBar

在开始下载之前使用 ProgressBar 实例化代码,您现在正在非 UI 线程中创建控件,这将导致新的 SynchronizationContext被创建并与控件本身一起绑定到该后台线程。这个 SynchronizationContext 有点不同,因为它是一个 WindowsFormsSynchronizationContext,它使用 Control.Invoke()Control.BeginInvoke() 方法与他们认为是 UI 的对象进行通信线。在这些方法内部 post 向 UI 的消息泵发送一条消息,告诉它在 UI 线程上执行指定的方法。

这似乎是出了问题的地方。通过在非 UI 线程中创建控件并因此在该线程中创建 WindowsFormsSynchronizationContextWebClient 现在将在调用事件处理程序时使用该上下文。 WebClient 将调用 WindowsFormsSynchronizationContext.Post(),后者又调用 Control.BeginInvoke() 以在同步上下文的线程上执行该调用。唯一的问题是:该线程没有处理 BeginInvoke 消息的消息循环。

  • 无消息循环 = BeginInvoke 消息不会被处理

  • 消息将不会被处理 = 没有调用指定的方法

  • 方法未被调用 = WebClientDownloadProgressChangedDownloadDataCompleted 事件永远不会被引发。

最后这一切再次归结为 WinForms 的黄金法则:

将所有 UI 相关工作留在 UI 线程!


EDIT:

As discussed in the comments/chat, if all you are doing is passing the progress bar to the WebClient's asynchronous methods, you can solve it like this and let Control.Invoke() create it on the UI thread and then return it for you:

Dim AddRPB As ProgressBar = Me.Invoke(Function() New ProgressBar)

AddHandler client.DownloadProgressChanged, AddressOf DownloadingProgress
AddHandler client.DownloadDataCompleted, AddressOf DownloadComplete

client.DownloadDataAsync(New Uri(WebLink), AddRPB)