Является ли этот код потокобезопасным

#multithreading #delphi #thread-safety #vcl

#многопоточность #delphi #потокобезопасность #vcl

Вопрос:

 // experimental code
procedure TFormMain.MyThumbnailProvider( const Path: Unicodestring; Width,
 Height: Integer; out Bitmap: TBitmap );
var
   AExtension: string;
   ARect: TRect;
begin
  AExtension := LowerCase( ExtractFileExt( Path ) );
  if AExtension = '.wmf' then
  begin
    ARect.Left := 0;
    ARect.Top := 0;
    ARect.Right := Width;
    ARect.Bottom := Height;
    Image1.Picture.LoadFromFile( Path ); // added at design time to form
    Bitmap := TBitmap.Create;
    Bitmap.Width := Width;
    Bitmap.Height := Height;
    Bitmap.Canvas.StretchDraw( ARect, Image1.Picture.Graphic );
  end;
end;
  

Отредактировано

 procedure TFormMain.MyThumbnailProvider( const Path: Unicodestring; Width, Height: Integer; out Bitmap: TBitmap );
var
  ARect: TRect;
  APicture: TPicture;
  AExtension: string;
begin
  // experimental code
  if FileExists( Path ) then
  begin
    AExtension := LowerCase( ExtractFileExt( Path ) );
    if AExtension = '.wmf' then
    begin
      ARect.Left := 0;
      ARect.Top := 0;
      ARect.Right := Width;
      ARect.Bottom := Height;
      APicture := TPicture.Create;
      try
        APicture.LoadFromFile( Path );
        Bitmap := TBitmap.Create;
        Bitmap.SetSize( Width, Height );
        Bitmap.IgnorePalette := True;
        Bitmap.PixelFormat := pf24bit;
        Bitmap.Transparent := False;
        Bitmap.Canvas.Lock; **// New**
        try
          Bitmap.Canvas.StretchDraw( ARect, APicture.Graphic );
        finally
          Bitmap.Canvas.Unlock;  **// New!**
        end;
      finally
        APicture.Free;
      end;
    end;
  end;
end;
  

Кажется, это полностью устраняет проблему с рисованием! По-видимому, вам приходится блокировать и разблокировать canvas при использовании Draw или StretchDraw, потому что в потоке DC его Bitmap.canvas иногда очищается из-за механизма кэширования объектов GDI в graphics.pas.

Смотрите http://qc.embarcadero.com/wc/qcmain.aspx?d=55871

Комментарии:

1. Вы спрашиваете о потокобезопасности, но я не вижу параллельных потоков в вашем вопросе. Почему вы беспокоитесь о потокобезопасности?

2. Вы слышали о библиотеке VirtualShellTools Джима Куэнемана ? Он отлично справляется с отображением файлов так, как это делает Explorer, и даже обрабатывает просмотр миниатюр.

3. Ограбление. Да, я много лет пользовался библиотекой VirtualShellTools Джима Куэнемана. Это было сделано очень хорошо, примерно до Delphi 2009 или 2010. С тех пор я не могу заставить его установить, и, насколько я знаю, он некоторое время не обновлялся. Даже группы пользователей Jim почти не проявляют активности более года. Я думаю, что Джим сейчас занят другими вещами… Plasmatech, похоже, также прекратила разработку оболочки, оставив только JamShellBrower в качестве единственной жизнеспособной оболочки vcl … которая, кстати, очень хороша, имеет хорошую поддержку, а также последние обновления.

Ответ №1:

Нет, из-за этого:

 Image1.Picture.LoadFromFile( Path );
/// [...]
Bitmap.Canvas.StretchDraw( ARect, Image1.Picture.Graphic );
  

Вы можете работать только с элементами управления VCL из основного потока VCL.

Комментарии:

1. Но если бы он создал новый TPicture объект для использования потоком, он мог бы использовать его для загрузки файла вместо того, который предоставляется TImage элементом управления, и это сработало бы, верно?

2. Да, объект TPicture кажется потокобезопасным, но в папке с 12 файлами wmf 3 неверны? Смотрите скриншот.

Ответ №2:

В общем случае код VCL не потокобезопасен, и это относится к большинству объектов VCL, доступных для использования.

Вы сказали:

Это кажется потокобезопасным, потому что в потоке не создаются исключения, но изображения кажутся частично пустыми или отображаются неправильно?

«Отсутствие исключений» не является показателем «потокобезопасности». Это то же самое, что сказать «Я поехал на работу и не разбился, так что моя машина устойчива к авариям».

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

  • На самом деле вам повезло, если вы получаете исключения из-за проблем с потоками, другие проблемы может быть сложнее отследить или даже осознать, что они возникают.
  • Вы можете столкнуться с взаимоблокировками, но если это происходит в фоновом потоке, вы можете даже не осознавать этого.
  • Неправильное поведение (как вы сообщаете), обычно из-за условий гонки, в которых:
    • Некоторый код будет взаимодействовать с объектом, пока он находится в несогласованном состоянии, что обычно приводит к крайне непредсказуемому поведению.
    • Данные неправильно «отбрасываются», потому что изменения одной подпрограммы немедленно перезаписывают другие.
  • Низкая производительность; да, плохо реализованные многопоточные решения могут серьезно снизить производительность.

Когда вы говорите «изображения кажутся частично пустыми или неправильно отрисовываются», возникает важный вопрос: всегда ли одни и те же изображения ведут себя неправильно, одинаковым образом? Если это так, то проблема может заключаться просто в том, что элемент управления, который вы используете для загрузки изображений, имеет проблемы с этими конкретными файлами.

Вы на самом деле запускаете несколько потоков? Я не видел в вашем коде ничего, что указывало бы на это.
Вы пробовали запускать однопоточный, чтобы подтвердить, действительно ли это проблема с потоками?


Редактировать
Тогда самым простым решением, вероятно, будет:

  • Определите пользовательское значение message const, для которого вы можете реализовать обработчик сообщений.
  • Внедрите обработчик сообщений для сообщения
  • Измените существующий procedure TFormMain.MyThumbnailProvider , чтобы он мог синхронизироваться с основным потоком VCL, и передайте работу синхронизированному обработчику.

Следующий вызовет ваш пользовательский обработчик в основном потоке VCL и будет ждать возврата.

 procedure TFormMain.MyThumbnailProvider( const Path: Unicodestring; 
  Width, Height: Integer; out Bitmap: TBitmap );
var
  LThumnailData: TThumbnailData; //Assuming an appropriately defined record
begin
  LThumbnailData.FPath := Path;
  LThumbnailData.FWidth := Width;
  LThumbnailData.FHeight := Height;
  LThumbnailData.FBitmap := nil;
  SendMessage(Self.Handle, <Your Message Const>, 0, Longint(@LThumbnailData));
  Bitmap := LThumbnailData.FBitmap;
end;
  

ПРАВКА2
Запрошен дополнительный пример кода:
Объявление сообщения const.

 const
  //Each distinct message must have its own unique ref number.
  //It's recommended to start at WM_APP for custom numbers.
  MSG_THUMBNAILINFO = WM_APP   0;
  

Объявление типа записи. Действительно просто, но вам тоже нужен указатель.

 type
  PThumbnailData = ^TThumbnailData;
  TThumbnailData = record
    FPath: Unicodestring;
    FWidth, FHeight: Integer;
    FBitmap: TBitmap;
  end;
  

Объявляем обработчик сообщений.

 procedure MSGThumbnailInfo(var Message: TMessage); message MSG_THUMBNAILINFO;
  

Реализация обработчика сообщений.

 procedure TForm3.MSGThumbnailInfo(var Message: TMessage);
var
  LThumbnailData: PThumbnailData;
begin
  LThumbnailData := Pointer(Message.LParam);

  //The rest of your code goes here.
  //Don't forget to set LThumbnailData^.FBitmap before done.

  Message.Result := 0;
  inherited;
end;
  

Комментарии:

1. Когда вы говорите «изображения кажутся частично пустыми или неправильно отрисовываются», возникает важный вопрос: всегда ли одни и те же изображения ведут себя неправильно, одинаковым образом?

2. «Когда вы говорите «изображения кажутся частично пустыми или отображаются неправильно», возникает важный вопрос: всегда ли одни и те же изображения ведут себя неправильно, одним и тем же образом?» Нет … разные эскизы отображаются неправильно каждый раз, когда я выбираю папку. Иногда почти все миниатюры отображаются правильно, в то время как в других случаях другой набор миниатюр отображается неправильно. Да, запущено несколько потоков. Причина, по которой вы не видели код, заключается в том, что код находится в JamShellBrowser, которым я не могу поделиться.

3. Как определить TThumbnailData и написать сообщение? Мы нашли это растровое изображение. Холст. StretchDraw (aRect, APicture. Графический); может быть причиной проблемы. Является растровым. Холст. StretchDraw потокобезопасен?

4. Смотрите последнюю правку. Проблема решена с помощью Canvas. Блокировка и холст. Разблокировать.

5. @Bill: Да, проблема, которую вы заметили , может быть решена, но, как уже отмечалось, это не означает, что больше нет проблем с потоками. Для этого вы должны тщательно изучить код, который вы вызываете. Например, APicture := TPicture.Create; глядя на реализацию TPicture.Create , этот вызов может выполнить отложенную инициализацию двух глобальных объектов. Если первый вызов выполняется из нескольких потоков одновременно, вы, вероятно, получите небольшую утечку памяти. К счастью, это не так уж плохо, но в VCL между этой строкой и точкой, где вы блокируете Canvas, происходит множество вещей.