1

I have a simple program which takes screenshots of the screen a few times per second. I created a simple code which does that and I can run it as many times as I want and it works alright. But when I put the same code into a thread and run it, the memory usage starts rising until the application runs out of resources (in about 10 seconds) and then the thread gets stuck of course.

For testing I have a form with two buttons. One runs the mentioned code and the second one starts a thread which runs the same code. I can even hold Enter key on the first button and there is no memory leak but when I click the second button the thread instantly keeps rising the memory usage (I can even stop it using stop_thread variable but the memory usage stays high).

...

type
  TMyThread = class(TThread)
  protected
    procedure Execute; override;
  public
    constructor Create;
    destructor Destroy; override;
  end;

var
  Form1: TForm1;
  stop_thread: Boolean;
  my_thread: TMyThread;

...

constructor TMyThread.Create();
begin
  inherited Create(true);
  FreeOnTerminate:=true;
  Suspended:=true;
end;

destructor TMyThread.Destroy;
begin
  inherited;
end;

procedure TMyThread.Execute;
var screen_bmp: TBitmap;
    desktop_hdc: HDC;
begin
  while(stop_thread=false)do
  begin
    screen_bmp:=TBitmap.Create;
    screen_bmp.PixelFormat:=pf32bit;
    screen_bmp.Height:=Screen.Height;
    screen_bmp.Width:=Screen.Width;
    desktop_hdc:=GetWindowDC(GetDesktopWindow);
    BitBlt(screen_bmp.Canvas.Handle, 0, 0, Screen.Width, Screen.Height, desktop_hdc, 0, 0, SRCCOPY);
    ReleaseDC(GetDesktopWindow, desktop_hdc);
    screen_bmp.Free;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var screen_bmp: TBitmap;
    desktop_hdc: HDC;
begin
  screen_bmp:=TBitmap.Create;
  screen_bmp.PixelFormat:=pf32bit;
  screen_bmp.Height:=Screen.Height;
  screen_bmp.Width:=Screen.Width;
  desktop_hdc:=GetWindowDC(GetDesktopWindow);
  BitBlt(screen_bmp.Canvas.Handle, 0, 0, Screen.Width, Screen.Height, desktop_hdc, 0, 0, SRCCOPY);
  ReleaseDC(GetDesktopWindow, desktop_hdc);
  screen_bmp.Free;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  stop_thread:=false;
  Button2.Enabled:=false;
  my_thread:=TMyThread.Create;
  my_thread.Resume;
end;

I know the problem has something to do with the BitBlt line, because without it there is no memory leak. But I don't understand what and why is happening. And why it isn't happening when the code is running from the main thread. Even when I put the Button1 code into a cycle and run it endlessly from the main thread, the memory usage stays low. What's the difference?

Thank you for any advice!

jano152
  • 147
  • 1
  • 7
  • 5
    Delphi's `TCanvas` isn't threadsafe (http://qc.embarcadero.com/wc/qcmain.aspx?d=43018). If I were you I would do this with raw Win32 API calls. – David Heffernan Jan 12 '17 at 10:40
  • Thank you, I think that fixed the problem. When I run the BitBlt part of the code (GetWindowDC, BitBlt, ReleaseDC) within Synchronize procedure the memory leak is gone. But I'm still not sure what was happening. – jano152 Jan 12 '17 at 10:51
  • 1
    The synchronize call defeats the purpose of the thread? So you need to do what David advises... – whosrdaddy Jan 12 '17 at 12:06
  • Just a very slightly. Because the real program has much longer code in MyThread than this test version. And that three lines called through synchronize are very fast, so the main thread stays fully responsive while MyThread is running in the loop doing other things. But I'm not sure about what you suggest. How will using Canvas.Lock help solve this memory leak problem? I think that the not thread safe usage of bitmap is a separate problem. Or isn't it? – jano152 Jan 12 '17 at 12:26
  • 2
    Have you tried Locking the canvas? – kobik Jan 12 '17 at 13:17
  • I tried it and it helped too. My bad, you (all) were right. Thank you! (But I still don't understand why locking helped or why there was a memory leak in the first place.) – jano152 Jan 12 '17 at 13:21
  • 2
    @jano152: Because the main UI thread regularly destroys open `HDC` handles for `TCanvas` objects that are not actively being used. Using `TCanvas.Lock()` lets that mechanism know to not destroy any `HDC` that your thread is still using at the time of the sweep. – Remy Lebeau Jan 12 '17 at 22:21
  • @RemyLebeau - surely that would tend lead to a double delete problem rather than a memory leak? I imagine (but don't know) that BitBlt creates a copy of the bitmap that would normally be freed in the ReleaseDC operation, but in a thread by the time it is executed the device context is no longer valid and the memory does not get freed. – Dsm Jan 13 '17 at 09:59
  • When working with `HDC`s, objects are usually selected into it (fonts, bitmaps, etc) to facilitate operations, replacing what was previously selected. If the `HDC` gets destroyed unexpectedly before the previous objects are selected back in, that can cause resource leaks. – Remy Lebeau Jan 13 '17 at 19:11

0 Answers0