【问题标题】:Threads are freezing main form线程冻结主窗体
【发布时间】:2020-02-17 00:36:43
【问题描述】:

我想运行多个线程。每个线程都应将 JPEG 转换为位图。转换有效,但我的整个应用程序始终使用 12%-13% 的 CPU。我有一个 8 核 CPU,所以看起来整个应用程序只使用一个内核。此外,当线程工作时,主窗体被冻结并且没有响应。

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics,
  Controls, Forms, Jpeg, Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    Str: TMemoryStream;
    procedure OnTerminate(Sender: TObject);
  end;

  TMakeThumbThread= class(TThread)
  private
    FStream: TStream;
  public
    FBmp: TBitmap;    
    constructor Create(Str: TStream);
    procedure Execute; override;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

constructor TMakeThumbThread.Create(Str: TStream);
begin
  inherited Create(True);
  FStream := Str;
  FreeOnTerminate := True;
end;

procedure TMakeThumbThread.Execute;
var Jpg: TJpegImage;
begin
  FBmp := TBitmap.Create;
  FBmp.PixelFormat := pf32bit;
  FBmp.Width := 300;
  FBmp.Height := 200;

  Jpg := TJpegImage.Create;
  FStream.Position := 0;
  Jpg.LoadFromStream(FStream);
  FBmp.Canvas.Draw(0,0, Jpg);
  Jpg.Free;

  DoTerminate;
  FBmp.Free;
end;

procedure TForm1.Button1Click(Sender: TObject);
var F: TFileStream;
    i: Integer;
    MT: TMakeThumbThread;
begin
  Str := TMemoryStream.Create;
  F := TFileStream.Create('test.jpg', fmOpenRead or fmShareDenyWrite);
  Str.CopyFrom(F, F.Size);
  F.Free;

  for i:=0 to 500 do begin
    MT := TMakeThumbThread.Create(Str);
    MT.OnTerminate := OnTerminate;
    MT.Execute;
  end;
end;

procedure TForm1.OnTerminate(Sender: TObject);
var Bmp: TBitmap;
begin
  Bmp := TMakeThumbThread(Sender).FBmp;
  Form1.Canvas.Draw(1,1, Bmp );
end;

end.

【问题讨论】:

  • 你必须学会​​保护你的物品。 从不X := TX.Create; {a lot of code} X.Free总是X := TX.Create; try {a lot of code} finally X.Free; end。 (在一种情况下,您似乎完全忘记了Free。)

标签: multithreading delphi delphi-7


【解决方案1】:

您在主线程的上下文中手动调用线程的Execute() 方法。不要那样做!这就是你的用户界面冻结的原因。您正在创建处于暂停状态的线程并且永远不会恢复它们。

你需要改变这一行:

MT.Execute;

到此:

MT.Resume;

或者这个:

MT.Start;

取决于您使用的 Delphi 版本。

您的代码还有其他几个问题。

  • VCL 的TBitmap 类不是完全线程安全的。在工作线程中使用TBitmap 时,您必须Lock() TBitmap.Canvas,以防止主线程意外地从TBitmap 中提取GDI 资源。

  • 您正在与多个线程共享一个 TMemoryStream,以使它们都同时加载同一个 JPG 图像。除非您使用同步对象(如TCriticalSectionTMutex)包装对TMemoryStream 的访问,否则这将不起作用。或者,另一种选择是使用TCustomMemoryStream 创建共享单个内存块的多个流。否则,您最好将 JPG 文件名传递给每个线程并让 Execute() 调用 TJpegImage.LoadFromFile() 而不是 TJpegImage.LoadFromStream()

  • 您在Execute() 的末尾调用FBmp.Free(),但随后您在OnTerminate 事件处理程序中访问FBmp。您需要延迟对FBmp.Free() 的调用,直到OnTerminate 事件处理程序退出之后,例如在线程的析构函数中。

  • 您正在从表单的OnPaint 事件外部直接在TForm.Canvas 上绘制位图。因此,一旦您的表单出于任何原因需要重新绘制自身,您绘制的图像就会丢失。如果您希望图像在表单的生命周期内保持不变,您需要保存它们并在OnPaint 事件触发时绘制它们。或者,您可以简单地将它们分配给TImage 组件,让它们为您处理绘图。

【讨论】:

  • 确实有帮助!非常感谢。你知道如何在没有 TBitmap 的情况下将 JPEG 转换为位图吗?
  • @Tom 这是一个完全不同的无关问题。请不要指望它会在另一个答案的 cmets 中得到回答。
  • @JerryDodge 我期待某种“无法完成”的快速回答。但你是对的。
  • @Tom 为什么需要将其转换为 BMP?您可以将原始 JPG Draw() 原样添加到您的表单中。
  • 可以做到,只是TJpeg不行。查看外部 DLL,例如 LibJpeg(或者更确切地说是 LibJpeg Turbo)或 OpenCV。这些在内存缓冲区上工作。但请注意:即使您设法找到 Delphi 的导入单元,学习曲线也相当陡峭。另一方面:你为什么要这样做?如果 TJPEG/TBitmap 适合您,请坚持使用。话虽如此:LibJpeg Turbo 的速度要快得多,所以如果性能成为问题,这绝对是一个可行的方法。
猜你喜欢
  • 1970-01-01
  • 2018-02-25
  • 1970-01-01
  • 2019-07-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多