Это может быть не совсем точно, но вот то, что я придумал в ходе некоторого тестирования и при помощи справочного источника :
Без / перед созданием ProgressBar
WebClient
работает с SynchronizationContexts
для отправки данных обратно в поток пользовательского интерфейса и вызова его обработчиков событий (как и BackgroundWorker
). Когда вы вызываете один из его Async
методов, WebClient
немедленно создает асинхронную операцию, связанную с SynchronizationContext
вызывающего потока. Если контекст не существует, создается новый и привязывается к этому потоку.
Если это делается в обработчике событий RunWorkerAsync
без (или до) создания ProgressBar
, для потока BackgroundWorker
будет создан новый контекст синхронизации.
Пока все хорошо. Все по-прежнему работает, но обработчики событий будут выполняться в фоновом потоке, а не в потоке пользовательского интерфейса.
Создание ProgressBar
до начала загрузки
Имея код создания ProgressBar
до начала загрузки, вы теперь создаете элемент управления в потоке, не являющемся пользовательским интерфейсом, что приведет к созданию нового SynchronizationContext
и его привязке к этому фоновому потоку вместе с сам контроль. Этот SynchronizationContext
немного отличается тем, что это WindowsFormsSynchronizationContext
, который использует методы Control.Invoke()
и Control.BeginInvoke()
для связи с тем, что они считают потоком пользовательского интерфейса. Внутренне эти методы отправляют сообщение в обработчик сообщений пользовательского интерфейса, сообщая ему о выполнении указанного метода в потоке пользовательского интерфейса.
Похоже, что здесь все идет не так. Создав элемент управления в потоке без пользовательского интерфейса и, таким образом, создав WindowsFormsSynchronizationContext
в этом потоке, WebClient
теперь будет использовать этот контекст при вызове обработчиков событий. WebClient
вызовет WindowsFormsSynchronizationContext.Post()
, что, в свою очередь, вызовет Control.BeginInvoke()
для выполнения этого вызова в потоке контекста синхронизации. Единственная проблема: у этого потока нет цикла сообщений, который будет обрабатывать сообщение BeginInvoke
.
Без цикла обработки сообщений = BeginInvoke
сообщение не будет обработано
Сообщение не будет обработано = ничего не вызывает указанный метод
Метод не вызван = WebClient
* DownloadProgressChanged
или DownloadDataCompleted
события никогда не будут вызваны.
В конце концов, все это снова сводится к золотому правилу WinForms:
Оставьте всю работу, связанную с пользовательским интерфейсом, в потоке пользовательского интерфейса!
EDIT:
Как обсуждалось в комментариях / чате, если все, что вы делаете, это передаете индикатор выполнения асинхронным методам WebClient
, вы можете решить это следующим образом и позволить Control.Invoke()
создать его в потоке пользовательского интерфейса, а затем верните его вам:
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)