在以下情况下我需要你的帮助。我知道它已被多次讨论过,应该使用线程,使用同步/关键部分等等。因此,不要因为再次提出这个问题而责怪我,因为在我的情况下,既没有同步,也没有关键部分有助于在TThread中处理TBitmap。
我使用Delphi XE,使用GlobalUseDirect2D的Firemonkey应用程序:= True;
我需要使用GlobalUseDirect2D,因为我画了很多,我需要快速绘图。仍在禁用GlobalUseDirect2D或使用GlobalUseGPUCanvas:= True,我的问题消失了,但这不是一个选项!
确定。所以这是一个其他项目的简单实现,但想法是显示图像缩略图。 首先,我构建一个项目列表(TImageData),然后我启动一个线程来加载图像缩略图。 当滚动(使用TScrollBar)时,我调用Arrange方法来排列表单上的项目,而不是调用Invalidate来重新绘制显示区域;
问题是某些缩略图是空白或未完全加载(已损坏)。
经过多次实验后,我发现图像被破坏了;
因此。如果我构建一个项目列表,然后启动缩略图线程,并在线程运行时不对表单执行任何操作(不要更改滚动条位置/不调整表单大小/不移动光标)然后一切都很精细。一切都很好;
如果我构建了一个项目列表,那么启动缩略图线程并在线程运行时开始滚动(更改滚动条位置 - 它调用Arrange + Invalidate方法),我的缩略图(不是全部)变得损坏。
因为我认为这可能是因为我的缩略图线程可以访问Items,同时当我调用Arrange时,主线程也会访问这些项目,这会产生一些干扰。 所以我尝试使用Synchronize和Critical部分,但它没有帮助。 我不知道如何以及在何处使用它们,因为它没有必要。为什么?我发现这种腐败发生的时候。见号6;
经过多次实验(再次),事实证明它很奇怪:
在线程运行时开始更改ScrollBar的位置 3.1 ScrollBar调用Arrange; 3.2 ScrollBar调用Invalidate;
结果:= BAD THUMBNAILS;
为什么我说这是"怪异的"? 我在表单中添加了另一个滚动条。现在我有2个滚动条。右边一个是调用Arrange + Invalidate的滚动条; 第二个ScrollBar只是没有;
所以当我这样做时:
4.Result:=同样的。也就是说,我仍然会损坏缩略图。
这很奇怪,不是吗?至少我不明白为什么会这样。 那么请告诉我如何解决它?
以及我展示我的意思的视频:https://youtu.be/dfe111odrUM
type
TImageData = class (TObject)
public
idPath:String;
idImage:TBitmap;
idloaded:Boolean;
x, y:Single;
w, h:Integer;
iCriticalSection:TRTLCriticalSection;
constructor Create;
destructor destroy; override;
end;
TImageThread = class(TThread)
private
tfileslist:TObjectList;
ttChangeHandle: THandle;
ttShutdownHandle: THandle;
ttPaused:Boolean;
ttCriticalSection:TCriticalSection;
procedure DoFolderItemChange;
protected
procedure Execute; override;
public
constructor Create(fileslist:TObjectList); reintroduce;
destructor Destroy; override;
procedure Shutdown;
procedure Reset;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
SR: TSearchRec;
ImageData:TImageData;
path:String;
begin
Path:= 'D:\Images\';
if FindFirst(Path + '*.*', faAnyFile, SR) = 0 then
begin
repeat
if (SR.Attr <> faDirectory) and (Pos ('.jpg', SR.Name) > 0) then
begin
ImageData:= TImageData.Create;
ImageData.idPath:= Path + SR.Name;
datalist.Add(ImageData);
end;
until FindNext(SR) <> 0;
FindClose(SR);
end;
arrange;
ImageThread.Reset;
end;
procedure TImageThread.Execute;
var
Events: array[0..1] of THandle;
WaitResult: DWORD;
ImageData:TImageData;
I:Integer;
begin
Events[0] := ttChangeHandle;
Events[1] := ttShutdownHandle;
while not Terminated do begin
WaitResult := WaitForMultipleObjects(2, @Events[0], FALSE, INFINITE);
if WaitResult = WAIT_OBJECT_0 then begin
if Assigned(tfileslist) then begin
for I:= 0 to tfileslist.Count - 1 do begin
ImageData:= TImageData(tfileslist.Items[I]);
try
ImageData.idImage.LoadThumbnailFromFile(ImageData.idPath, 128, 128);
except
on E : Exception do
begin
//ShowMessage('Exception class name = '+E.ClassName);
ShowMessage(ImageData.idPath + ' ----- Exception message = '+E.Message);
end;
end;
ImageData.idloaded:= True;
end;
end;
end;
self.Synchronize(nil, procedure ()
begin
Form1.Button1.Text:= 'DONE';
beep;
end);
end;
end;
procedure TForm1.ScrollBar1Change(Sender: TObject);
begin
arrange;
Invalidate;
end;
procedure TForm1.arrange;
var
I:Integer;
ImageData, ImageDataP:TImageData;
begin
for I:= 0 to datalist.Count - 1 do begin
ImageData:= TImageData(datalist.Items[I]);
if I = 0 then begin
ImageData.x:= 50;
ImageData.y:= 50 - ScrollBar1.Value;
end else begin
ImageDataP:= TImageData(datalist.Items[I - 1]);
ImageData.x:= ImageDataP.x + 128;
ImageData.y:= ImageDataP.y;
if ImageData.x + 128 > Width then begin
ImageData.x:= 50;
ImageData.y:= ImageDataP.y + 128 + 10;
end;
end;
end;
end;
procedure TForm1.FormPaint(Sender: TObject; Canvas: TCanvas;
const ARect: TRectF);
var
I:Integer;
ImageData:TImageData;
begin
Canvas.BeginScene();
try
for I:= 0 to datalist.Count - 1 do begin
ImageData:= TImageData(datalist.Items[I]);
if Assigned(ImageData.idImage) and ImageData.idloaded then begin
Canvas.DrawBitmap(ImageData.idImage, RectF(0, 0, ImageData.idImage.Width, ImageData.idImage.Height),
RectF(ImageData.x, ImageData.y, ImageData.x + 128, ImageData.y + 128), 1, True );
end;
end;
finally
Canvas.EndScene;
end;
end;
答案 0 :(得分:4)
我相信您的问题是您没有意识到TBitmap不是线程安全的。其他一切对我来说都很好。要解决此问题,请更改项目中的以下代码行
ImageData.idImage.LoadThumbnailFromFile(ImageData.idPath, 256, 256);
以便它在Synchronize块中。
Synchronize(nil, procedure ()
begin
ImageData.idImage.LoadThumbnailFromFile(ImageData.idPath, 256, 256);
end);
我在您的项目中尝试了此更改,并没有注意到任何无法加载的位图。
答案 1 :(得分:-2)
我试图说FIREMONKEY Bitmap存在问题,没有人听我说。我仍然找到了解决方案而且我是正确的:)
所以,正如我之前所说,当我使用VCL.Graphics.TBitmap时,我在加载图像缩略图时没有遇到任何问题,并按照我在本例中的方式显示它们。我使用了TBitmap.Canvas.Lock,我使用了Synchronize
使用Firemonkey这种方式不起作用,问题隐藏在TBitmap.LoadThumbnailFromFile
方法中。
当我尝试
时Synchronize(nil, procedure ()
begin
ImageData.idImage.LoadThumbnailFromFile(ImageData.idPath, 128, 128);
end);
然后缩略图被加载到主线程中,我的应用程序冻结,直到所有缩略图都被加载,但缩略图被正确加载;
如果你看一下LoadThumbnailFromFile方法:
procedure TBitmap.LoadThumbnailFromFile(const AFileName: string; const AFitWidth, AFitHeight: Single;
const UseEmbedded: Boolean = True);
var
Surf: TBitmapSurface;
begin
Surf := TBitmapSurface.Create;
try
if TBitmapCodecManager.LoadThumbnailFromFile(AFileName, AFitWidth, AFitHeight, UseEmbedded, Surf) then
Assign(Surf)
else
raise EThumbnailLoadingFailed.CreateFMT(SThumbnailLoadingFailedNamed, [AFileName]);
finally
Surf.Free;
end;
end;
事实证明Assign(Surf)
引起了问题!!!
你所要做的只是Synchronize
它,只有它,但不是整个LoadThumbnailFromFile
方法;
喜欢这个:
procedure GetThumbnail(DestBMP:TBitmap; W, H:Integer; Path:String; Thread:TThread);
var
Surf: TBitmapSurface;
begin
Surf := TBitmapSurface.Create;
try
if TBitmapCodecManager.LoadThumbnailFromFile(Path, W, H, False, Surf) then
begin
Thread.Synchronize(nil, procedure ()
begin
DestBMP.Assign(Surf) ;
end);
end;
finally
Surf.Free;
end;
end;
在我的示例中更改TImageThread.Execute
并自行尝试;
这样,应用程序在后台线程中加载缩略图,仍然可以正确加载所有图像,并且可以在加载缩略图时滚动/调整应用程序的大小。
procedure TImageThread.Execute;
var
Events: array[0..1] of THandle;
WaitResult: DWORD;
ImageData:TImageData;
I:Integer;
Surf: TBitmapSurface;
begin
Events[0] := ttChangeHandle;
Events[1] := ttShutdownHandle;
while not Terminated do begin
WaitResult := WaitForMultipleObjects(2, @Events[0], FALSE, INFINITE);
if WaitResult = WAIT_OBJECT_0 then begin
if Assigned(tfileslist) then begin
for I:= 0 to tfileslist.Count - 1 do begin
ImageData:= TImageData(tfileslist.Items[I]);
try
GetThumbnail(ImageData.idImage, 128, 128, ImageData.idPath,Self);
ImageData.idloaded:= True;
except
on E : Exception do
begin
//ShowMessage('Exception class name = '+E.ClassName);
ShowMessage(ImageData.idPath + ' ----- Exception message = '+E.Message);
end;
end;
end;
end;
end;
self.Synchronize(nil, procedure ()
begin
Form1.Button1.Text:= 'DONE';
beep;
end);
end;
end;