Многопоточные загрузки WinHttp - PullRequest
5 голосов
/ 14 ноября 2011

Я создаю приложение Delphi для загрузки файлов из Интернета и, если сервер поддерживает запрос диапазона, оно будет многопоточным. Ход выполнения также передается обратно в графический интерфейс.

Текущая модель программного обеспечения использует TThread компоненты. Графический интерфейс вызывает TDownloadThread, который затем порождает TDownloadPartThreads - это потоки, которые на самом деле выполняют загрузку через WinHttp.

Моя проблема : ЦП израсходован, даже для одной загрузки, когда загружается только 4 потока.

Мои предполагаемые причины:

  1. Я записываю в файл назначения каждые 8192 байта и был интересно, стоит ли буферизовать его перед записью в одном блоке?
  2. Обмен потоками осуществляется через Synchronize(MainForm.UpdateProgress(Downloaded, TotalSize)), что, как я слышал, ужасно, может быть, мне следует разделить объект между потоками, чтобы я мог получить к нему доступ с помощью таймера в главной форме для обновления прогресса?

Мои решения

  1. Пошаговая запись файла и запись только каждые x байтов.

  2. Обновите TThread компоненты для использования OmniThreadLibrary и отправьте данные обратно в основную форму. Каждый поток TDownloadPart станет IOmniWorker и отправит обратно свой прогресс, поделившись объектом с главной формой. Основная форма будет затем использовать таймер для обновления своего прогресса, например: ProgressBar1.Position := sharedDataObject.Progress;

Надеюсь, кто-то может указать мне правильное направление!

Ответы [ 4 ]

2 голосов
/ 14 ноября 2011

Я бы использовал общий объект для обновления состояния - так же, как вы предлагаете в своем втором решении.Если вы разделяете только 8-байтовый (4 недостаточно!) Размер файла и если вы убедитесь, что адрес каждого общего расположения выровнен по 8, вы можете использовать взаимосвязанные инструкции для изменения этого общего состояния, и вам не понадобится блокировка события.

Самым простым способом поддержания общего состояния была бы запись TGp8AlignedInt64 из модуля GpStuff , которая одинаково хорошо работала бы с решением на основе 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 из события timer в основной форме одновременно.

1 голос
/ 14 ноября 2011

Synchronize () может замедлить вашу программу, потому что у вас будут дополнительные потоки, ожидающие в главном потоке, однако это не обременительно для CPU.Ваш компьютер не будет выполнять больше работы, чем он уже был, но пользователь может заметить это, если это повлияет на скорость отклика графического интерфейса.

Запись на диск может быть интенсивной операцией ввода-вывода, но, опять же, ненагрузка на процессор.Иногда ваши антивирусные программы увеличивают загрузку ЦП при записи и чтении, а зашифрованная файловая система вносит небольшую дополнительную нагрузку на ЦП.

Поскольку ни одна из двух упомянутых вами проблем сами по себе не влияет на ЦПиспользование, изменение их не обязательно облегчит проблему.

Возможно, вы слишком часто обновляете графический интерфейс, или код, обновляющий графический интерфейс, слишком интенсивно загружает процессор?Что произойдет, если вы перестанете делать обратные вызовы для обновления графического интерфейса?Уменьшает ли это нагрузку на процессор?

Возможно, код, записывающий на диск, обрабатывает его каким-то образом?Как вы буферизируете данные?

Определенно выясните, какова реальная причина высокой загрузки ЦП.

Если вы обнаружите, что вашим узким местом является нечто иное, чем пропускная способность интернета, скорее всего,есть проблема.

1 голос
/ 14 ноября 2011

Мой совет - не «угадывать», что медленно, а использовать профилировщик и измерить , где сгорел процессор.Я подозреваю, что вы можете быть удивлены.

WinHTTP не работает медленно и не использует много процессора самостоятельно.Это намного быстрее, чем WinINet, и работает очень хорошо (по крайней мере, с плоским C API - или вы используете интерфейс COM?).Возможно, в вашем коде что-то не так.

О ваших вопросах:

  1. Запись размера фрагмента в 8192 байта имеет смысл и не будет намного быстрее (когдапо сравнению со скоростью загрузки потока HTTP), если вы используете больший буфер.Файловая система Windows обычно записывает данные на диск по 4 КБ и сама выполняет буферизацию.Просто попробуйте увеличить его (например, 65536), но я не думаю, что изменения будут заметны.

  2. Synchronize не так уж ужасно.Что вы можете сделать, это назвать его, только если вы меняете на несколько процентов (например, каждые 5% или 10%), а не каждый раз.Вы можете сделать это в потоке загрузки, просто добавив приватную переменную, содержащую последний заявленный размер.

Другой возможностью может быть использование некоторых свойств только для чтения (DownloadedSize + TotalSize: Int64) длякласс потока, затем обновите их содержимое во время загрузки.Затем используйте TTimer - или создайте пользовательское сообщение (WM_USER+...), затем используйте PostMessage() в потоке загрузки - в главном потоке графического интерфейса, чтобы при необходимости обновить индикатор выполнения для каждого потока.Это безопасно для чтения некоторых свойств из основного потока.

1 голос
/ 14 ноября 2011

Да - все зависит от частоты обновления графического интерфейса. TThread.Synchronize является наихудшим вариантом для обновления основных потоков - потоки загрузки вынуждены ждать, пока обновление GUI не будет выполнено, прежде чем они смогут продолжить. Следующим в списке являются обновления PostMessaging, которые занимают промежуточную позицию - потокам загрузки больше не нужно ждать, но если графический интерфейс пользователя не успевает за опубликованными сообщениями, он все равно замерзнет задолго до того, как будет ограничен лимит сообщений 10000 для WMQ достиг. Там, где есть много потоков и быстрых обновлений, таймер опрашивает некоторый подходящий объект уведомления / список / массив / все, что является разумным решением.

...