这个代码线程安全吗

Posted

技术标签:

【中文标题】这个代码线程安全吗【英文标题】:Is this code thread safe 【发布时间】:2011-04-26 12:48:21 【问题描述】:
// 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;

这似乎完全解决了绘图问题!显然,在使用 Draw 或 StretchDraw 时,您必须锁定和解锁画布,因为在线程中,由于 graphics.pas 中的 GDI 对象缓存机制,有时会清除其 Bitmap.canvas 的 DC。

见http://qc.embarcadero.com/wc/qcmain.aspx?d=55871

【问题讨论】:

您在询问线程安全,但我在您的问题中看不到并发线程。你为什么要关心线程安全? 你听说过 Jim Kueneman 的 VirtualShellTools library 吗?它在显示文件方面做得很好,就像资源管理器一样,它甚至可以处理缩略图视图。 罗布。是的,我多年来一直使用 Jim Kueneman 的 VirtualShellTools 库。它做得非常好,直到大约 Delphi 2009 或 2010。从那以后我无法安装它,据我所知,它已经有一段时间没有更新了。甚至 Jim 的用户组在一年多的时间里也几乎没有任何活动。我认为 Jim 现在正忙于其他事情...... Plasmatech 似乎也停止了 shell 开发,只剩下 JamShellBrower 作为唯一可行的 vcl shell ......顺便说一句非常好,有很好的支持以及最近更新。 【参考方案1】:

不,因为这个:

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

您只能在 VCL 主线程中使用 VCL 控件。

【讨论】:

但是如果他创建了一个新的TPicture 对象供线程使用,他可以使用它来加载文件而不是TImage 控件提供的文件,这样就可以了,对吧? 是的,TPicture 对象似乎是线程安全的,但在一个包含 12 个 wmf 文件的文件夹中,3 个不正确?看截图。【参考方案2】:

一般而言,VCL 代码不是线程安全的,这适用于大多数可供使用的 VCL 对象。

你说:

这似乎是线程安全的,因为线程中没有产生异常,但图像似乎部分空白或绘制不正确?

“无例外”并不表示“线程安全”。这就像说“我开车去上班,没有撞车,所以我的车是防撞的。”

线程问题高度依赖时间,并以多种方式表现出来——不仅仅是例外。要记住的重要一点是,线程问题可能会作为潜在缺陷存在数月,然后才会发生任何不愉快的事情。即便如此,它们通常很难以任何一致性度量来重现。

如果您遇到线程问题异常,您真的很幸运,其他问题可能更难以跟踪,甚至意识到它们正在发生。 您可能会遇到死锁,但如果它在后台线程中,您可能甚至都没有意识到。 不正确的行为(正如您所报告的那样),通常是由于竞争条件导致: 某些代码会在对象处于不一致状态时与其交互 - 通常会导致高度不可预测的行为。 数据被错误地“丢弃”,因为一个例程更改会立即覆盖另一个例程。 性能不佳;是的,执行不力的多线程解决方案会严重降低性能。

当您说“图像似乎部分空白或绘制不正确”时,一个重要的问题是:相同的图像是否总是以相同的方式出现异常?如果是这样,那么问题可能只是您用来加载图像的控件与这些特定文件有问题。

您实际上是在运行多个线程吗?我在您的代码中没有看到任何可以表明的内容。 您是否尝试过运行单线程来确认它是否真的是线程问题?


编辑 那么最简单的解决方案可能是:

定义一个自定义消息常量,您可以在该常量上实现消息处理程序。 为消息实现消息处理程序 修改您现有的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;

EDIT2 请求更多示例代码: 声明消息常量。

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;

【讨论】:

当您说“图像似乎部分空白或绘制不正确”时,一个重要的问题是:相同的图像是否总是以相同的方式出现异常? “当您说“图像似乎部分空白或绘制不正确”时,一个重要的问题是:相同的图像是否总是以相同的方式出现异常?不...每次我选择一个文件夹时,都不会正确绘制不同的缩略图。有时几乎所有缩略图都正确绘制,而其他时候一组不同的缩略图绘制不正确。是的,多个线程正在运行。您没有看到代码的原因是因为代码在 JamShellBrowser 中,我无法共享。 如何定义TThumbnailData并写消息?我们发现 Bitmap.Canvas.StretchDraw( ARect, APicture.Graphic );可能导致问题。 Bitmap.Canvas.StretchDraw 线程安全吗? 查看最新编辑。使用 Canvas.Lock 和 Canvas.Unlock 解决了问题。 @Bill:是的,您注意到的问题可能已经解决,但正如已经指出的,这并不意味着不再存在线程问题。为此,您必须仔细检查您调用的代码。例如。 APicture := TPicture.Create; 查看TPicture.Create 的实现,这个调用可以执行2 个全局对象的惰性初始化。如果第一次调用同时来自多个线程,您可能会遇到少量内存泄漏。幸运的是,这并不算太糟糕,但是在 VCL 中在那条线和锁定 Canvas 的点之间发生了很多事情。

以上是关于这个代码线程安全吗的主要内容,如果未能解决你的问题,请参考以下文章

golang 中的 os.File.Write() 线程安全吗?

django sql线程安全吗?

这个请求频率限制器线程安全吗?

SKNode线程安全吗?

Java静态初始化器线程安全吗?

mongodatabase mongocollection 线程安全吗