多线程WinHttp下载

时间:2011-11-14 14:53:06

标签: multithreading delphi winhttp omnithreadlibrary

我正在创建一个Delphi应用程序来从Internet下载文件,如果服务器支持范围请求,它将是多线程的。进度也被转发回GUI。

当前的软件模型使用TThread个组件。 GUI调用TDownloadThread然后生成TDownloadPartThreads - 这些是实际通过'WinHttp'进行下载的线程。

我的问题:即使只下载4个线程的下载,CPU也会用完。

我假设的原因:

  1. 我每8192字节写一次目标文件,而且是 想知道我是否应该在写入一个块之前缓冲它?
  2. 线程通信是通过Synchronize(MainForm.UpdateProgress(Downloaded, TotalSize))完成的,我听说是AWFUL要做的,也许我应该在线程之间共享一个对象,以便我可以使用主窗体上的计时器来访问它,以更新进度?< / LI>

    我的解决方案

    1. 错开文件写入,只写每x个字节。

    2. 更新TThread组件以使用OmniThreadLibrary并以某种方式将数据发送回主表单。然后,每个TDownloadPart线程将成为IOmniWorker,并通过与主表单共享对象来发回其进度。然后,主窗体将使用计时器更新其进度,例如:ProgressBar1.Position := sharedDataObject.Progress;

    3. 希望有人能指出我正确的方向!

4 个答案:

答案 0 :(得分:2)

我会使用共享对象来更新状态 - 就像你在第二个解决方案中建议的那样。如果您只共享一个8字节(4个不够!)的文件大小,并且如果您确保每个共享位置的地址是8对齐的,则可以使用互锁指令来修改此共享状态,并且您不需要锁定事件

维护共享状态的最简单方法是来自GpStuff单元的TGp8AlignedInt64记录,这对于OmniThreadLibrary或基于TThread的解决方案同样有效。

TGp8AlignedInt64 = record
  function  Add(value: int64): int64; inline;
  function  Addr: PInt64; inline;
  function  CAS(oldValue, newValue: int64): boolean;
  function  Decrement: int64; overload; inline;
  function  Decrement(value: int64): int64; overload; inline;
  function  Increment: int64; overload; inline;
  function  Increment(value: int64): int64; overload; inline;
  function  Subtract(value: int64): int64; inline;
  property Value: int64 read GetValue write SetValue;
end; 

此记录上的所有操作都是线程安全的,因此您可以安全地在工作线程中执行.Add并同时从主窗体中的timer事件调用.Value。

答案 1 :(得分:1)

是的 - 这一切都取决于GUI更新的频率。 TThread.Synchronize是主线程更新最糟糕的选择 - 下载线程被迫等待GUI更新,然后才能继续。列表中的下一个是PostMessaging更新,这填补了中间地带 - 下载线程不再需要等待,但是,如果GUI无法跟上发布的消息,它将在WMQ的10000消息限制之前很久就冻结到达。在有许多线程和快速更新的情况下,定时器轮询一些合适的通知对象/列表/数组/什么是明智的解决方案。

答案 2 :(得分:1)

我的建议不是猜测&#34;什么是慢,但使用分析器和测量 CPU被烧毁。我怀疑你可能会感到惊讶。

WinHTTP并不慢,并且本身不使用大量CPU。它比WinINet快得多,并且运行良好(至少使用扁平的C API - 或者您使用的是COM接口吗?)。也许你的代码有问题。

关于您的问题:

  1. 写入8192字节的块大小确实有意义,如果使用更大的缓冲区,则不会更快(与HTTP流下载速度相比)。 Windows文件系统通常将磁盘上的数据写入4 KB,并自行完成缓冲。试着让它变大(例如65536),但我不认为变化会很明显。

  2. Synchronize并不是那么糟糕。你可以做的只是在你改变一些百分比(例如每5%或10%)而不是每次改变时才调用它。您可以在下载线程中执行此操作,只需添加包含最新通知大小的私有变量。

  3. 另一种可能性是对线程类使用一些只读属性(DownloadedSize + TotalSize: Int64),然后在下载期间更新其内容。然后使用TTimer - 或创建自定义消息(WM_USER+...),然后在下载线程中使用PostMessage() - 在主GUI线程中为每个线程刷新进度条(如果需要) 。从主线程读取某些属性是安全的。

答案 3 :(得分:1)

Synchronize()可能会降低你的程序速度,因为你的主线程上会有你的额外线程等待,但是,这不是CPU的负担。您的计算机将不再进行任何工作,但如果用户的GUI响应速度很快,用户可能会注意到它。

写入磁盘可能是IO密集型的,但同样不会给CPU带来负担。有时您的防病毒程序会增加写入和读取时的CPU使用率,而加密文件系统会引入一点额外的CPU使用率。

由于您提到的两个问题都不会影响CPU使用率,因此更改它们不一定能解决问题。

也许您经常更新GUI,或者更新GUI的代码太过CPU密集?如果您停止回拨更新GUI会发生什么?它是否会降低CPU使用率?

也许写入磁盘的代码是以某种方式处理它的?你是如何缓冲数据的呢?

绝对找出高CPU使用率的真正原因。

如果您发现您的瓶颈不是互联网带宽,那么很可能存在问题。