【问题标题】:Webclient Download not starting when creating object创建对象时Webclient下载未开始
【发布时间】:2018-11-15 22:08:59
【问题描述】:

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

单行以某种方式中断了它,我不明白为什么。

【问题讨论】:

  • 如果你打电话给DownloadDataAsync,使用BackgroundWorker 是没有意义的。 BackgroundWorker 的重点是在辅助线程上工作。 DownloadDataAsync 的重点是在辅助线程上下载数据。如果您要在辅助线程上下载数据,BackgroundWorker 有什么意义?
  • 根据您发布的代码很难说,但问题很可能与您在使用时在辅助线程上创建 ProgressBar 的事实有关BackgroundWorker。这只是没有意义,因为控件是 UI 的一部分,因此本质上是前台。我建议你摆脱BackgroundWorker。如果您还有其他需要,请在 UI 线程上调用 DownloadDataAsync
  • 下载只是其中的一部分,请保持话题,为什么之前创建的时候不起作用,但创建后的时候起作用,这是个问题。
  • 他是话题。 jmcilhinney 的最后一条评论准确描述了您可能会遇到这种行为的原因:您正在后台线程中创建ProgressBar(这是一个 UI 元素)。这样做很糟糕,并且抛出异常的那条线可能是它停止工作的一个可能原因。始终确保将 所有 UI 相关工作留在 UI 线程 only
  • @VisualVincent 没有异常或错误,Sub 正常完成,除了在它之前创建对象时下载没有开始,在它之后创建它时一切都按预期工作,怎么办你解释一下?

标签: vb.net winforms webclient


【解决方案1】:

这可能并不完全准确,但这是我在Reference Source 的帮助下通过一些测试得出的结论:

没有/在ProgressBar 的实例化之前

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

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

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

在开始下载之前创建ProgressBar

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

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

  • 没有消息循环 = 不会处理 BeginInvoke 消息

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

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

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

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


编辑:

正如 cmets/chat 中所讨论的,如果您所做的只是将进度条传递给 WebClient 的异步方法,您可以像这样解决它,让 Control.Invoke() 在 UI 线程上创建它,然后还给你:

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)

【讨论】:

  • 我发现,即使我在 DownloadDataAsync 之后创建进度条,进度条也可以在progresschanged 触发器时找到(虽然我不确定如果它是一些小文件是否会)。我需要异步所以我可以传递一些数据,这样我就可以找到正在进行的进度条更改/下载完整事件。但从那以后我重写了代码并按预期创建了更多东西在主线程上。
  • @CruleD :好吧,您根本不应该在后台线程中创建控件:)。顺便说一句,我目前正在用更详细的解释编辑我的答案。所以请继续关注更新。
  • 为什么不呢,这是我第一次遇到这样的奇怪问题,当然有时您需要从某个在后台运行的任务创建控件。猜猜这只是那些奇怪的微软怪癖中的另一个。
  • @CruleD :无论如何,这不是“扭结”。在所有操作系统中,多线程代码很容易出现竞争条件。 Windows 中的每个 UI 线程都由一个消息泵驱动,该消息泵并不意味着在不同的线程之间被篡改(它应该如何知道哪个线程应该首先访问哪个控件,以及如果它们以错误的顺序执行会发生什么?)。编写线程安全代码始终取决于程序员,因为永远不会有一个全面的解决方案。
  • @CruleD : "Why not" - 您永远不需要在后台线程中创建控件。如果你觉得你需要这样做,那么你做错了什么,需要重新考虑你的模型。 数据繁重的处理应该在后台线程中处理,然后传递给为其添加/更新适当控件的UI线程。
猜你喜欢
  • 1970-01-01
  • 2014-06-26
  • 2012-05-04
  • 2018-02-21
  • 1970-01-01
  • 2019-06-18
  • 2013-12-27
  • 2010-09-13
  • 2020-12-01
相关资源
最近更新 更多