【发布时间】:2017-03-10 03:29:24
【问题描述】:
情况:我有一个程序启动一个线程在后台连续运行。当我终止此线程时,我间歇性地收到运行时错误 204(无效指针)和内存泄漏。
创建线程的代码(它创建在一个对象 TJEList 中,该对象有一个 OnLJ2DOSyncThreadNotification 方法,在通知上调用该方法,该通知使用线程中的一些 TStringList 对象执行一些操作):
FLJ2DOSyncThread:=TLJ2DOSyncThread.Create (True);
FLJ2DOSyncThread.NotifyEvent:=OnLJ2DOSyncThreadNotification;
FLJ2DOSyncThread.Start;
线程的Execute 代码:
FreeOnTerminate:=True;
//I create 5 StringList objects here - they are declared as private variables in the Thread
try
while Not Terminated do
begin
//Perform operation (which internally also checks for Terminated)
if Terminated then
Break;
//Perform different operation (which internally also checks for Terminated)
if Terminated then
Break;
//Perform different operation (which internally also checks for Terminated)
if Terminated then
Break;
//etc...
Sleep (1500);
end;
finally
//I FreeAndNil(...) all the TStringlist objects
end;
为了更好的衡量(虽然可能是多余的),我在线程中还有一个析构函数:
if Assigned (TStringList object) then FreeAndNil (TStringList object);
对于所有已创建的 TStringList 对象(然后调用 inherited;)。
我停止线程的代码是:
if Assigned(FLJ2DOSyncThread) then
if FLJ2DOSyncThread.Started then
FLJ2DOSyncThread.Terminate;
我调用代码从 TJEList 对象的析构函数中停止线程(在应用程序的主窗体关闭时调用它)。
问题: 有时,程序会干净地终止(没有内存泄漏或错误消息)。其他时候,我收到以下错误消息和内存泄漏(无论如何,内存泄漏消息出现在运行时错误消息之前):
我的问题:我如何确保线程总是可靠地终止(并因此被释放)?任何帮助和/或指导将不胜感激!
更新 20170310 5.08pm HKT:根据要求包括 MCVE 的代码
程序代码:
program ThreadIssueMCVE;
uses
System.StartUpCopy,
FMX.Forms,
frmMain in 'frmMain.pas' {fMain},
MyList in 'MyList.pas',
MyThread in 'MyThread.pas';
{$R *.res}
begin
{$IFDEF DEBUG}
System.ReportMemoryLeaksOnShutdown:=true;
{$ENDIF}
Application.Initialize;
Application.CreateForm(TfMain, fMain);
Application.Run;
end.
主窗体代码:
unit frmMain;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, MyList,
FMX.Controls.Presentation, FMX.StdCtrls;
type
TfMain = class(TForm)
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
FMyList: TMyList;
public
{ Public declarations }
end;
var
fMain: TfMain;
implementation
{$R *.fmx}
procedure TfMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
FreeAndNil(FMyList);
end;
procedure TfMain.FormCreate(Sender: TObject);
begin
FMyList:=TMyList.Create;
end;
end.
我的列表代码:
unit MyList;
interface
uses
System.Classes, System.SysUtils, MyThread;
type
TMyList = Class (TObject)
private
FSomeList: TStringList;
FMyThread: TMyThread;
protected
procedure OnMyThreadNotification (Sender: TObject);
procedure OnMyThreadTerminate (Sender: TObject);
procedure ActOnThreadResults (AList: TStringList);
public
procedure InitMyThread;
procedure StopMyThread;
constructor Create;
destructor Destroy; override;
property
SomeList: TStringList read FSomeList;
end;
implementation
{ TMyList }
constructor TMyList.Create;
begin
inherited Create;
FSomeList:=TStringList.Create;
InitMyThread;
end;
destructor TMyList.Destroy;
begin
StopMyThread;
FreeAndNil(FSomeList);
inherited Destroy;
end;
procedure TMyList.ActOnThreadResults (AList: TStringList);
var
i: Integer;
begin
for i:= 0 to AList.Count-1 do
begin
if FMyThread.CheckTerminated then
exit;
FSomeList.Add(AList.Strings[i]);
end;
end;
procedure TMyList.InitMyThread;
begin
FMyThread:=TMyThread.Create (True);
FMyThread.NotifyEvent:=OnMyThreadNotification;
FMyThread.OnTerminate:=OnMyThreadTerminate;
FMyThread.Start;
end;
procedure TMyList.OnMyThreadNotification(Sender: TObject);
var
fullList: TStringList;
begin
if (FMyThread.FList4.Count>0) or (FMyThread.FList5.Count>0) then
begin
fullList:=TStringList.Create;
try
fullList.Text:=FMyThread.FList4.Text + FMyThread.FList5.Text;
ActOnThreadResults(fullList);
finally
FreeAndNil (fullList);
end;
end;
end;
procedure TMyList.OnMyThreadTerminate(Sender: TObject);
begin
FreeAndNil(FMyThread);
end;
procedure TMyList.StopMyThread;
begin
FMyThread.Terminate;
end;
end.
MyThread 代码:
unit MyThread;
interface
uses
System.Classes, System.SysUtils;
type
TMyThread = Class (TThread)
private
FLastRun: TDateTime;
FList1: TStringList;
FList2: TStringList;
procedure SomeProcess;
procedure SomeOtherProcess;
protected
procedure Execute; override;
public
NotifyEvent: TNotifyEvent;
FList3: TStringList;
FList4: TStringList;
FList5: TStringList;
destructor Destroy; override;
End;
implementation
destructor TMyThread.Destroy;
begin
FreeAndNil(FList1);
FreeAndNil(FList2);
FreeAndNil(FList3);
FreeAndNil(FList4);
FreeAndNil(FList5);
inherited;
end;
procedure TMyThread.SomeOtherProcess;
var i: integer;
begin
for i := 1 to 1000000 do
begin
if Terminated then
break;
//do some stuff here
FList5.Add(i.ToString);
end;
end;
procedure TMyThread.SomeProcess;
var i: integer;
begin
for i := 1 to 1000000 do
begin
if Terminated then
break;
//do some stuff here
FList4.Add(i.ToString);
end;
end;
procedure TMyThread.Execute;
var
boolCheck: Boolean;
begin
NameThreadForDebugging('Thread with issues');
FreeOnTerminate:=False;
FList1:=TStringList.Create;
FList2:=TStringList.Create;
FList3:=TStringList.Create;
FList4:=TStringList.Create;
FList5:=TStringList.Create;
FLastRun:=Now; //i get this from an ini file normally
try
while Not Terminated do
begin
if Terminated then
Break;
FList1.Clear;
FList2.Clear;
FList3.Clear;
FList4.Clear;
FList5.Clear;
if Terminated then
Break;
SomeProcess;
if Terminated then
Break;
SomeOtherProcess;
if Terminated then
Break;
SomeProcess;
if Terminated then
Break;
SomeOtherProcess;
if Terminated then
Break;
SomeProcess;
if Terminated then
Break;
if (FList4.Count>0) OR (FList5.Count>0) then
boolCheck:=True;
if Terminated then
Break;
if boolCheck then
NotifyEvent(Self);
if Terminated then
Break;
Sleep (2000);
if Terminated then
Break;
FLastRun:=Now; //i save to ini file as well
end;
finally
//i save to ini file the last run
FreeAndNil(FList1);
FreeAndNil(FList2);
FreeAndNil(FList3);
FreeAndNil(FList4);
FreeAndNil(FList5);
end;
end;
end.
【问题讨论】:
-
也许问题可能与不等待线程完成有关。所以尝试在
Terminate调用之后调用WaitFor这样的FLJ2DOSyncThread.WaitFor;方法。 -
@RRUZ 感谢您的评论。我曾考虑过这一点并尝试添加 WaitFor,但是当我这样做时,我收到以下错误:“
引发异常类 EThread,消息为‘线程错误:句柄无效 (6)’。” -
@Rohit 您正在使用
FreeOnTerminate=True,因此使用WaitFor()是不安全的,这样做是一种可能导致崩溃的竞争条件。FreeOnTerminate应该只用于启动和忘记线程。这不是那种情况,所以当你完成使用它时,摆脱FreeOnTerminate并明确地Free线程 -
问题是你持有对线程的引用并将
FreeOnTerminate设置为True。当线程对象被销毁时,您不会尝试清除该引用。雷米的评论中对此进行了解释。 -
请在致电
Free之前停止测试Assigned。Free已经测试了对象是否已分配,因此您的代码添加了不必要的混乱。