Загрузка Webclient не начинается при создании объекта - PullRequest
0 голосов
/ 16 ноября 2018

У меня есть WebClient в BackgroundWorker, но по какой-то причине он не загружается, когда я создаю объект перед его запуском.Он работает нормально, когда в основном потоке.


Как это не работает:

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

Эта единственная строка как-то ломает ее, я не понимаю почему.

1 Ответ

0 голосов
/ 16 ноября 2018

Это может быть не совсем точно, но вот то, что я придумал в ходе некоторого тестирования и при помощи справочного источника :

Без / перед созданием 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)
...