5

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

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

我的问题:CPU 已经用完了,即使是一次下载,只有 4 个线程下载。

我推测的原因:

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

我的解决方案

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

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

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

4

4 回答 4

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 并同时在主窗体中从计时器事件中调用 .Value 。

于 2011-11-14T15:35:00.543 回答
1

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

于 2011-11-14T15:15:46.033 回答
1

我的建议不是“猜测”什么是慢的,而是使用分析器并测量CPU 被烧毁的位置。我怀疑你可能会感到惊讶。

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

关于您的问题:

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

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

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

于 2011-11-14T16:07:41.240 回答
1

Synchronize() 可能会减慢您的程序速度,因为您将有额外的线程在主线程上等待,但是,它不会对 CPU 造成负担。您的计算机将不会做更多的工作,但如果它影响对 GUI 的响应,用户可能会注意到它。

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

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

也许您更新 GUI 过于频繁,或者更新 GUI 的代码太占用 CPU?如果您停止回调以更新 GUI,会发生什么?它会降低CPU使用率吗?

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

一定要找出 CPU 使用率高的真正原因是什么。

如果您发现瓶颈不是 Internet 带宽,则很可能存在问题。

于 2011-11-14T16:46:47.927 回答