This might not be completely accurate, but here's what I came up with through some testing and with help from the Reference Source:
Without/before the instantiation of the ProgressBar
The WebClient
works with SynchronizationContexts
in order to post data back to the UI thread and invoke its event handlers (as does the BackgroundWorker
). When you call one of its Async
methods the WebClient
immediately creates an asynchronous operation that is bound to the SynchronizationContext
of the calling thread. If a context doesn't exist, a new one is created and bound to that thread.
If this is done in the RunWorkerAsync
event handler without (or before) creating the ProgressBar
, a new synchronization context will be created for the BackgroundWorker
's thread.
So far so good. Everything still works but the event handlers will be executed in a background thread rather than the UI thread.
Creating the ProgressBar
before starting the download
With the ProgressBar
instantiation code in place before the download is started you're now creating a control in a non-UI thread, which will result in a new SynchronizationContext
being created and bound to that background thread along with the control itself. This SynchronizationContext
is a little different in that it is a WindowsFormsSynchronizationContext
, which uses the Control.Invoke()
and Control.BeginInvoke()
methods to communicate with what they consider to be the UI thread. Internally these methods post a message to the UI's message pump, telling it to execute the specified method on the UI thread.
This appears to be where things go wrong. By creating a control in a non-UI thread and thus creating a WindowsFormsSynchronizationContext
in that thread, the WebClient
will now use that context when invoking the event handlers. The WebClient
will call WindowsFormsSynchronizationContext.Post()
, which in turn calls Control.BeginInvoke()
to execute that call on the synchronization context's thread. The only problem is: That thread has no message loop that will handle the BeginInvoke
message.
No message loop = The BeginInvoke
message won't be handled
The message won't be handled = Nothing calls the specified method
The method isn't called = The WebClient
's DownloadProgressChanged
or DownloadDataCompleted
events will never be raised.
In the end all this just once again boils down to the golden rule of WinForms:
Leave all UI related work on the UI thread!
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)