Delphi XE5 - 在TBitmap上操作时线程和内存泄漏

时间:2014-02-06 11:06:51

标签: delphi delphi-xe5

应用程序按照我的意愿工作但存在相当大的内存泄漏。 限制一个线程的每个事件都会给我4个TBitmaps和2个丢失的TStrokeBrush。

程序DrawSine();在Execute in Synchronize语句中触发:

procedure SineThread.DrawSine();
var
  sin_T : Extended;
  Point2 : TPoint;
  I : Integer;
begin
  TempBitmap.SetSize(Twidth, Theight);
  TempBitmap.Canvas.BeginScene();
  TempBitmap.Canvas.Stroke.Kind := TBrushKind.bkSolid;
  TempBitmap.Canvas.Stroke.Color := claLime;
  TempBitmap.Canvas.Clear(TAlphaColorRec.Black);
  for I := 0 to Twidth do
  begin
      sin_T := Sin(((I - Tphas)/100.0) * Tfreq);

      Point2.X := Round(I);
      Point2.Y := Round(sin_T * Tampl) + Round(Theight/2.0);

      if I = 0 then
      begin
        Point1.X := Round(I);
        Point1.Y := Round(sin_T * Tampl) + Round(Theight/2.0);
        TempBitmap.Canvas.DrawLine(Point1, Point2, 1.0, TempBrush);
      end
      else
      begin
        if I = Twidth then
        begin
          TempBitmap.Canvas.DrawLine(Point1, Point2, 1.0, TempBrush);
          Point1.X := Round(I);
          Point1.Y := Round(Theight/2.0);
        end
        else
        begin
          TempBitmap.Canvas.DrawLine(Point1, Point2, 1.0, TempBrush);
          Point1.X := Point2.X;
          Point1.Y := Point2.Y;
        end;
     end;
  end;
  TempBitmap.Canvas.EndScene();
end;

SineThread构造函数和析构函数:

constructor SineThread.Create(CreateSuspended: Boolean);
begin
  inherited Create(CreateSuspended);
  try
    TempBitmap := TBitmap.Create();
    TempBrush := TStrokeBrush.Create(TBrushKind.bkSolid, TAlphaColorRec.White);
  finally
    Twidth := 0;
    Theight := 0;
    Tampl := 0;
    Tphas := 0;
    Tfreq := 0;
    Point1 := Point(0,0);
  end;
end;

destructor SineThread.Destroy();
begin
  inherited Destroy();
  TempBitmap.Free();
  TempBrush.Free();
end;

完成线程时的OnTerminate如下:

procedure TForm1.OnTerminateProc1(Sender: TObject);
var
  TempStream : TMemoryStream;
begin
  try
    TempStream := TMemoryStream.Create();
  finally
    (Sender as SineThread).GetBitmap.SaveToStream(TempStream);
    Image1.Bitmap.LoadFromStream(TempStream);
    TempStream.Free();
  end;
end;

每当TrackBars上的值发生变化时,都会启动Trigger()过程:

procedure TForm1.Trigger(Sender: TObject);
var
  sine1_thread : SineThread;
  sine2_thread : SineThread;
  sineSum_thread : SineSumThread;
begin
  try
    begin
      sine1_thread := SineThread.Create(True);
      sine2_thread := SineThread.Create(True);
      sineSum_thread := SineSumThread.Create(True);
    end;
  finally
    begin
      sine1_thread.SetSineParams(TrackBar1.Value, TrackBar2.Value, TrackBar3.Value);
      sine1_thread.SetImageParams(Trunc(Image1.Width), Trunc(Image1.Height));
      sine1_thread.FreeOnTerminate := True;
      sine1_thread.OnTerminate := OnTerminateProc1;
      sine1_thread.Start();
      sine2_thread.SetSineParams(TrackBar4.Value, TrackBar5.Value, TrackBar6.Value);
      sine2_thread.SetImageParams(Trunc(Image2.Width), Trunc(Image2.Height));
      sine2_thread.FreeOnTerminate := True;
      sine2_thread.OnTerminate := OnTerminateProc2;
      sine2_thread.Start();
      sineSum_thread.SetSineParams(TrackBar1.Value, TrackBar2.Value, TrackBar3.Value, TrackBar4.Value, TrackBar5.Value, TrackBar6.Value);
      sineSum_thread.SetImageParams(Trunc(Image3.Width), Trunc(Image3.Height));
      sineSum_thread.FreeOnTerminate := True;
      sineSum_thread.OnTerminate := OnTerminateProc3;
      sineSum_thread.Start();
    end;
  end;
end;

2 个答案:

答案 0 :(得分:5)

似乎线程没有被破坏。因为它们在终止时被释放,这似乎很奇怪。你设置FreeOnTerminate,所以如果线程终止,它们将被销毁。

让我们假设要终止的线程。在这种情况下,解释是您的析构函数缺少覆盖指令。它应该像这样声明:

destructor Destroy; override;

我的通灵调试技巧(并非绝对可靠)告诉我你错过了覆盖。因此,当调用Destroy时,基类方法运行而不是您的。

追踪泄漏的最有效方法是使用完整版的FastMM。正确配置后,将为与泄漏关联的分配提供堆栈跟踪。以及许多其他有用的东西,以帮助早期找到缺陷。

不要在构造函数的实现中使用finally。如果引发异常,则实例将被销毁,因此finally块无意义。

使用正确的资源获取模式:

obj := TMyClass.Create;
try
  obj.Foo; // do stuff with obj
finally
  obj.Free;
end;

在编写它时,构造函数中的异常引发将导致您对未初始化的实例变量调用Free

以与其获取相反的顺序释放资源。这意味着你的析构函数应该写成:

destructor SineThread.Destroy;
begin
  TempBrush.Free;
  TempBitmap.Free;
  inherited;
end;

finally中的TForm1.Trigger也是错误的。无论如何,finally块都会运行。如果由于某种原因您未能创建对象,则不得继续执行,就好像未发生该故障一样。您使用finally来保护资源。您获取了一个资源,并使用finally块来确保无论发生什么都释放它。

您的程序中绝对不需要线程。正如您在上一个问题中所解释的那样,并在此处再次提到,您可以使用Synchronize将所有工作放在主线程上。这使得线程无效。我不知道你为什么选择使用线程。也许你认为通过这样做,你的程序会表现得更好。情况并非总是如此,当你按照自己的方式实现线程时肯定不会这样。

编程在最好的时候是很难的,没有不必要的复杂性。特别是当你还没有掌握这门语言的时候。我的建议是在继续进行线程等高级主题之前先做到这一点。

最后,您必须学会提供完整但有效的例子来解决这类问题。你省略了相当多的代码,如果我是对的,那么导致泄漏的最重要的代码就被省略了。

答案 1 :(得分:2)

要记住的一个一般规则是:

  

当对象的构造函数引发异常时,它的析构函数是   自动调用。

因此不需要try..finally中的SineThread.Create序列。 在对象的析构函数中,将inherited作为最后一项调用。

constructor SineThread.Create(CreateSuspended: Boolean);
begin
  inherited Create(CreateSuspended);
  TempBitmap := TBitmap.Create();
  TempBrush := TStrokeBrush.Create(TBrushKind.bkSolid, TAlphaColorRec.White);
  Twidth := 0;
  Theight := 0;
  Tampl := 0;
  Tphas := 0;
  Tfreq := 0;
  Point1 := Point(0,0);
end;

destructor SineThread.Destroy();
begin
  TempBitmap.Free();
  TempBrush.Free();
  inherited;
end;
{p>同样适用于OnTerminateProc1

procedure TForm1.OnTerminateProc1(Sender: TObject);
var
  TempStream : TMemoryStream;
begin
  TempStream := TMemoryStream.Create();
  try
    (Sender as SineThread).GetBitmap.SaveToStream(TempStream);
    Image1.Bitmap.LoadFromStream(TempStream);
  finally
    TempStream.Free();
  end;
end;

try..finally中不需要Trigger()

procedure TForm1.Trigger(Sender: TObject);
var
  sine1_thread : SineThread;
  sine2_thread : SineThread;
  sineSum_thread : SineSumThread;
begin
 sine1_thread := SineThread.Create(True);
 sine2_thread := SineThread.Create(True);
 sineSum_thread := SineSumThread.Create(True);
 sine1_thread.SetSineParams(TrackBar1.Value, TrackBar2.Value, TrackBar3.Value);
 sine1_thread.SetImageParams(Trunc(Image1.Width), Trunc(Image1.Height));
 sine1_thread.FreeOnTerminate := True;
 sine1_thread.OnTerminate := OnTerminateProc1;
 sine1_thread.Start();
 sine2_thread.SetSineParams(TrackBar4.Value, TrackBar5.Value, TrackBar6.Value);
 sine2_thread.SetImageParams(Trunc(Image2.Width), Trunc(Image2.Height));
 sine2_thread.FreeOnTerminate := True;
 sine2_thread.OnTerminate := OnTerminateProc2;
 sine2_thread.Start();
 sineSum_thread.SetSineParams(TrackBar1.Value, TrackBar2.Value, TrackBar3.Value, TrackBar4.Value, TrackBar5.Value, TrackBar6.Value);
 sineSum_thread.SetImageParams(Trunc(Image3.Width), Trunc(Image3.Height));
 sineSum_thread.FreeOnTerminate := True;
 sineSum_thread.OnTerminate := OnTerminateProc3;
 sineSum_thread.Start();
end;