这个代码线程安全吗
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 的点之间发生了很多事情。以上是关于这个代码线程安全吗的主要内容,如果未能解决你的问题,请参考以下文章