Delphi XE5 - Threads and memory leak when operation on TBitmap

Application works as I'd like but there are quite big memory leakage. Every event that throttles one thread gives me 4 TBitmaps and 2 TStrokeBrush that are lost.

The procedure DrawSine(); is triggered in Execute in Synchronize statement:

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 and Destructor:

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 when finishing thread looks like:

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;

The Trigger() procedure is started every the the value on TrackBars change:

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;

Answers


It seems that the threads are not being destroyed. Since they are freed on terminate that seems odd. You set FreeOnTerminate, so if the threads terminate they will be destroyed.

Let us assume that the threads to terminate. In which case the explanation is that your destructor is missing override directive. It should be declared like this:

destructor Destroy; override;

My psychic debugging skills (not infallible) tell me that you missed the override. So when Destroy is called, the base class method runs and not yours.

The most effective way to track down leaks is to use the full version of FastMM. When configured correctly that will give stack traces for the allocation associated with a leak. And lots of other useful stuff to help find defects earlier.

Don't use finally in a constructor's implementation. If an exception is raised then, the instance will be destroyed and so your finally block is pointless.

Use the correct resource acquisition pattern:

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

As you write it, an exception raise in the constructor will lead to you calling Free on an uninitialized instance variable.

Deallocate resource in reverse order to their acquisition. That means that your destructor should be written:

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

The finally in TForm1.Trigger is also wrong. A finally block runs no matter what. If for some reason you fail to create an object, you must not carry on as if that failure did not happen. You use finally to protect a resource. You acquire a resource, and use the finally block to make sure that you release it no matter what.

There's absolutely no need for threads in your program. As you explained in your previous question, and mentioned again here, you use Synchronize to put all the work onto the main threads. This renders the threads nugatory. I don't know why you chose to use threads. Perhaps you thought that by doing so, your program would perform better. That is not always the case, and certainly not when you implement threading the way you have done.

Programming is hard enough at the best of times, without needless complexity. Especially when you have not yet mastered the language. My advice is to do that first before moving on to advanced topics like threading.

Finally, you must learn to present complete, but cut-down examples for questions like this. You omitted quite a bit of code, and if I am right, the most important bit of code, that which causes the leak, was omitted.


One general rule to remember is this one:

When an object's constructor raises an exception, it's destructor is called automatically.

So the try..finally sequence in SineThread.Create is not needed. In an object's destructor, call inherited as last item.

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;

same goes for 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;

no need for try..finally inTrigger() :

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;

Need Your Help

How to execute insert query using Entity Framework

c# sql .net entity-framework

I am trying to execute a insert query using Entity Framework.

Firing off Google Analytics from C# does not work

c# google-analytics

I have the following code that I have in C# but I'm not seeing anything come through on Fiddler2? I've looked at many questions on here and in general although there seems to be different ways of ...