TThread 详解

TThread 详解 
我们常有工作线程和主线程之分,工作线程负责作一些后台操作,比如接收邮件;主线程负责界面上的一些显示。工作线程的好处在某些时候是不言而喻的,你的主界面可以响应任何操作,而背后的线程却在默默地工作。
VCL中,工作线程执行在Execute方法中,你必须从TThread继承一个类并覆盖Execute方法,在这个方法中,所有代码都是在另一个 线程中执行的,除此之外,你的线程类的其他方法都在主线程执行,包括构造方法,析构方法,Resume等,很多人常常忽略了这一点。

最简单的一个线程类如下:

TMyThread = class(TThread)

protected 

procedure Execute; override; 

end; 

在Execute中的代码,有一个技术要点,如果你的代码执行时间很短,像这样,Sleep(1000),那没有关系;如果是这样Sleep (10000),10秒,那么你就不能直接这样写了,须把这10秒拆分成10个1秒,然后判断Terminated属性,像下面这样: 

procedure TMyThread.Execute; 

var

i: Integer; 

begin 

for i := 0 to 9 do 

if not Terminated then 

Sleep(1000) 

else

Break;

end;

这样写有什么好处呢,想想你要关闭程序,在关闭的时候调用MyThread.Free,这个时候线程并没有马上结束,它调用WaitFor,等待 Execute执行完后才能释放。你的程序就必须等10秒以后才能关闭,受得了吗。如果像上面那样写,在程序关闭时,调用Free之后,它顶多再等一秒就 会关闭。为什么?答案得去线程类的Destroy中找,它会先调用Terminate方法,在这个方法里面它把Terminated设为True(仅此而 已,很多人以为是结束线程,其实不是)。请记住这一切是在主线程中操作的,所以和Execute是并行执行的。既然Terminated属性已为 Ture,那么在Execute中判断之后,当然就Break了,Execute执行完毕,线程类也正常释放。 

或者有人说,TThread可以设FreeOnTerminate属性为True,线程类就能自动释放。除非你的线程执行的任务很简单,不然,还是不要去理会这个属性,一切由你来操作,才能使线程更灵活强大。 

接下来的问题是如何使工作线程和主线程很好的通信,很多时候主线程必须得到工作线程的通知,才能做出响应。比如接收邮件,工作线程向服务器收取邮件,收取完毕之后,它得通知主线程收到多少封邮件,主线程才能弹出一个窗口通知用户。

在VCL中,我们可以用两种方法,一种是向主线程中的窗体发送消息,另一种是使用异步事件。第一种方法其实没有第二种来得方便。想想线程类中的OnTerminate事件,这个事件由线程函数的堆栈引起,却在主线程执行。

事实上,真正的线程函数是这个:

function ThreadProc(Thread: TThread): Integer; 

函数里面有Thread.Execute,这就是为什么Execute是在其他线程中执行,该方法执行之后,有如下句: 

Thread.DoTerminate; 

而线程类的DoTerminate方法里面是 

if Assigned(FOnTerminate) then Synchronize(CallOnTerminate); 

显然Synchronize方法使得CallOnTerminate在主线程中执行,而CallOnTerminate里面的代码其实就是: 

if Assigned(FOnTerminate) then FOnTerminate(Self);

只要Execute方法一执行完就发生OnTerminate事件。不过有一点是必须注意,OnTerminate事件发生后,线程类不一定会释 放,只有在FreeOnTerminate为True之后,才会Thread.Free。看一下ThreadProc函数就知道。 

依照Onterminate事件,我们可以设计自己的异步事件。 

Synchronize方法只能传进一个无参数的方法类型,但我们的事件经常是要带一些参数的,这个稍加思考就可以得到解决,即在线程类中保存参数,触发事件前先设置参数,再调用异步事件,参数复杂的可以用记录或者类来实现。 

假设这样,上面的代码每睡一秒,线程即向外面引发一次事件,我们的类可以这样设计: 

TSecondEvent = procedure (Second: Integer) of object;
TMyThread = class(TThread)
private
FSecond: Integer;
FSecondEvent: TSecondEvent;
procedure CallSecondEvent;
protected
procedure Execute; override;
public
property SencondEvent: TSecondEvent read FSecondEvent
write FSecondEvent;
end;

{ TMyThread }

procedure TMyThread.CallSecondEvent;
begin
if Assigned(FSecondEvent) then
FSecondEvent(FSecond);
end;

procedure TMyThread.Execute;
var
i: Integer;
begin
for i := 0 to 9 do
if not Terminated then
begin
Sleep(1000);
FSecond := i;
Synchronize(CallSecondEvent);
end
else
Break;
end; 
在主窗体中假设我们这样操作线程:

procedure TForm1.Button1Click(Sender: TObject);
begin
MyThread := TMyThread.Create(true);
MyThread.OnTerminate := ThreadTerminate;
MyThread.SencondEvent := SecondEvent;
MyThread.Resume;
end;

procedure TForm1.ThreadTerminate(Sender: TObject);
begin
ShowMessage('ok');
end;

procedure TForm1.SecondEvent(Second: Integer);
begin
Edit1.Text := IntToStr(Second);
end;

我们将每隔一秒就得到一次通知并在Edit中显示出来。

现在我们已经知道如何正确使用Execute方法,以及如何在主线程与工作线程之间通信了。但问题还没有结束,有一种情况出乎我的意料之外,即如果 线程中有一些资源,Execute正在使用这些资源,而主线程要释放这个线程,这个线程在释放的过程中会释放掉资源。想想会不会有问题呢,两个线程,一个 在使用资源,一个在释放资源,会出现什么情况呢, 

用下面代码来说明:

type
TMyClass = class
private
FSecond: Integer;
public
procedure SleepOneSecond;
end;

TMyThread = class(TThread)
private
FMyClass: TMyClass;
protected
procedure Execute; override;
public
constructor MyCreate(CreateSuspended: Boolean);
destructor Destroy; override;
end;

implementation

{ TMyThread }

constructor TMyThread.MyCreate(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
FMyClass := TMyClass.Create;
end;

destructor TMyThread.Destroy;
begin
FMyClass.Free;
FMyClass := nil;
inherited;
end;

procedure TMyThread.Execute;
var
i: Integer;
begin
for i := 0 to 9 do
FMyClass.SleepOneSecond;
end;

{ TMyClass }

procedure TMyClass.SleepOneSecond;
begin
FSecond := 0;
Sleep(1000);
end;

end. 

用下面的代码来调用上面的类:

procedure TForm1.Button1Click(Sender: TObject);
begin
MyThread := TMyThread.MyCreate(true);
MyThread.OnTerminate := ThreadTerminate;
MyThread.Resume;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
MyThread.Free;
end;

先点击Button1创建一个线程,再点击Button2释放该类,出现什么情况呢,违法访问,是的,MyThread.Free时,MyClass被释放掉了 

FMyClass.Free;

FMyClass := nil; 

而此时Execute却还在执行,并且调用MyClass的方法,当然就出现违法访问。对于这种情况,有什么办法来防止呢,我想到一种方法,即在线程类中使用一个成员,假设为FFinished,在Execute方法中有如下的形式: 

FFinished := False; 

try 

//... ...

finally 

FFinished := True;

End; 

接着在线程类的Destroy中有如下形式: 

While not FFinished do 

Sleep(100); 

MyClass.Free; 

这样便能保证MyClass能被正确释放。 

线程是一种很有用的技术。但使用不当,常使人头痛。在CSDN论坛上看到一些人问,我的窗口在线程中调用为什么出错,主线程怎么向其他线程发送消息等等,其实,我们在抱怨线程难用时,也要想想我们使用的方法对不对,只要遵循一些正确的使用规则,线程其实很简单。 

后记 

上面有一处代码有些奇怪:FMyClass.Free; FMyClass := nil;如果你只写FMyClass.Free,线程类还不会出现异常,即调用FMyClass.SleepOneSecond不会出错。我在主线程中试了下面的代码

MyClass := TMyClass.Create;

MyClass.SleepOneSecond; 

MyClass.Free; 

MyClass.SleepOneSecond; 

同样也不会出错,但关闭程序时就出错了,如果是这样:

MyClass := TMyClass.Create;

MyClass.SleepOneSecond; 

MyClass.Free;

MyThread := TMyThread.MyCreate(true);

MyThread.OnTerminate := ThreadTerminate;

MyThread.Resume;

MyClass.SleepOneSecond;

马上就出错。所以这个和线程类无线,应该是Delphi对于堆栈空间的释放规则,我想MyClass.Free之后,该对象在堆栈上空间还是保留 着,只是允许其他资源使用这个空间,所以接着调用下面这一句MyClass.SleepOneSecond就不会出错,当程序退出时可能对堆栈作一些清理 导致出错。而如果MyClass.Free之后即创建MyThread,大概MyClass的空间已经被MyThread使用,所以再调用 MyClass.SleepOneSecond就出错了。 
展开阅读全文

TThread是抽象的。为什么不能创建TThread实例

11-11

rn 为什么说啊rnrnrn这是源码:rnrn TThread = class //--------------------------------rn privatern$IFDEF MSWINDOWSrn FHandle: THandle;rn FThreadID: THandle;rn$ENDIFrn$IFDEF LINUXrn // ** FThreadID is not THandle in Linux **rn FThreadID: Cardinal;rn FCreateSuspendedSem: TSemaphore;rn FInitialSuspendDone: Boolean;rn$ENDIFrn FCreateSuspended: Boolean;rn FTerminated: Boolean;rn FSuspended: Boolean;rn FFreeOnTerminate: Boolean;rn FFinished: Boolean;rn FReturnValue: Integer;rn FOnTerminate: TNotifyEvent;rn FSynchronize: TSynchronizeRecord;rn FFatalException: TObject;rn procedure CallOnTerminate;rn class procedure Synchronize(ASyncRec: PSynchronizeRecord); overload;rn$IFDEF MSWINDOWSrn function GetPriority: TThreadPriority;rn procedure SetPriority(Value: TThreadPriority);rn$ENDIFrn$IFDEF LINUXrn // ** Priority is an Integer value in Linuxrn function GetPriority: Integer;rn procedure SetPriority(Value: Integer);rn function GetPolicy: Integer;rn procedure SetPolicy(Value: Integer);rn$ENDIFrn procedure SetSuspended(Value: Boolean);rn protectedrn procedure CheckThreadError(ErrCode: Integer); overload;rn procedure CheckThreadError(Success: Boolean); overload;rn procedure DoTerminate; virtual;rn procedure Execute; virtual; abstract;rn procedure Synchronize(Method: TThreadMethod); overload;rn property ReturnValue: Integer read FReturnValue write FReturnValue;rn property Terminated: Boolean read FTerminated;rn publicrn constructor Create(CreateSuspended: Boolean);rn destructor Destroy; override;rn procedure AfterConstruction; override;rn procedure Resume;rn procedure Suspend;rn procedure Terminate;rn function WaitFor: LongWord;rn class procedure Synchronize(AThread: TThread; AMethod: TThreadMethod); overload;rn class procedure StaticSynchronize(AThread: TThread; AMethod: TThreadMethod);rn property FatalException: TObject read FFatalException;rn property FreeOnTerminate: Boolean read FFreeOnTerminate write FFreeOnTerminate;rn$IFDEF MSWINDOWSrn property Handle: THandle read FHandle;rn property Priority: TThreadPriority read GetPriority write SetPriority;rn$ENDIFrn$IFDEF LINUXrn // ** Priority is an Integer **rn property Priority: Integer read GetPriority write SetPriority;rn property Policy: Integer read GetPolicy write SetPolicy;rn$ENDIFrn property Suspended: Boolean read FSuspended write SetSuspended;rn$IFDEF MSWINDOWSrn property ThreadID: THandle read FThreadID;rn$ENDIFrn$IFDEF LINUXrn // ** ThreadId is Cardinal **rn property ThreadID: Cardinal read FThreadID;rn$ENDIFrn property OnTerminate: TNotifyEvent read FOnTerminate write FOnTerminate;rn end;rn 论坛

TThread Synchronize and "Out of Memory"

05-18

创建TThread时有如下提示:rnrn//---------------------------------------------------------------------------rnrn// Important: Methods and properties of objects in VCL can only bern// used in a method called using Synchronize, for example:rn//rn// Synchronize(&UpdateCaption);rn//rn// where UpdateCaption could look like:rn//rn// void __fastcall GetHistThread::UpdateCaption()rn// rn// Form1->Caption = "Updated in a thread";rn// rn//---------------------------------------------------------------------------rnrn我在我的线程中使用了TStringList, TADOConnection, TADODataSet等是否属于VCL?是否要像上面提示的那样都放入Synchronize()中去做?rnrn目前我碰到了非常古怪的问题,就是一直会报Out of Memory的错误,但实际上我机器还有很多很多的内存(分配到100M左右时就出现内存不足的错误,但其实我的机器总共有8G的内存)。rnrn出错的TThread::Execute()函数如下:rn[code=C/C++]void __fastcall TGetHistThread::Execute()rnrn DWORD dwTotalCount, dwCurrCount;rnrn this->m_etErrorLevel = xelSystem;rn m_strErrorMsg.sprintf("[历史数据缓冲线程]启动历史数据缓冲线程...");rn Synchronize(&AddErrorMsg);rnrn dwTotalCount = 0;rn dwCurrCount = 0;rnrn while( !Terminated )rn rn if ( !ConnectDB() )rn rn this->m_etErrorLevel = xelSystem;rn m_strErrorMsg.sprintf("[历史数据缓冲线程]连接数据库失败,请检查配置.");rn Synchronize(&AddErrorMsg);rn MySleep(5000);rnrn continue;rn rnrn TStringList * pTables = Get5MTableNames();rn TDateTime dtCurProdDt = m_dtStartDt;rnrn TADODataSet * pDst = new TADODataSet(NULL);rn pDst->Connection = m_pclAdoConn;rnrn while(!Terminated)rn rn dwCurrCount = 0;rnrn m_strErrorMsg.sprintf("Producer: Waiting for mutex[%d]...", g_stGlobalVar.m_nProd);rn Synchronize(&AddErrorMsg);rn WaitForSingleObject(g_stGlobalVar.m_hMutex[g_stGlobalVar.m_nProd],INFINITE);rn m_strErrorMsg.sprintf("Producer: Got Lock mutex[%d]. Continue.", g_stGlobalVar.m_nProd);rn Synchronize(&AddErrorMsg);rnrn TDateTime weekStart = dtCurProdDt;rn TDateTime weekEnd = IncWeek(dtCurProdDt, 1);rn m_strErrorMsg.sprintf("Producer: Buffering data from %s to %s",rn weekStart.DateTimeString().t_str(), weekEnd.DateTimeString().t_str());rn Synchronize(&AddErrorMsg);rnrn // if reach end, set ret to false;rn if(weekEnd >= m_dtEndDt) rn m_strErrorMsg.sprintf("Producer: Reach end datetime = %s", m_dtEndDt.DateTimeString());rn Synchronize(&AddErrorMsg);rnrn delete pTables;rn pTables = NULL;rnrn if(pDst->Active)rn pDst->Close();rn delete pDst;rn pDst = NULL;rnrn Terminate();rn rnrn // set str dates and times for sql searchrn DateTimeUtil sdt, edt;rn sdt.SetStrDateTime(IncMinute(weekStart, -5));rn edt.SetStrDateTime(weekEnd);rnrn //std::list tempList;rn for (int i = 0; i < pTables->Count; ++i)rn rn if(i >= 3000)rn continue;rnrn String strTableName = pTables->Strings[i];rn if(pDst->Active)rn pDst->Close();rn pDst->CommandType = cmdText;rn pDst->CommandText = "\rn select * \rn from " + strTableName + " \rn where ((QuotaDate > " + sdt.strDate + ") or (QuotaDate = " + sdt.strDate + " and QuotaTime > " + sdt.strTime + ")) \rn and ((QuotaDate < " + edt.strDate + ") or (QuotaDate = " + edt.strDate + " and QuotaTime <= " + edt.strTime + ")) \rn ";rn pDst->Open();rnrn if (pDst->IsEmpty()) rn pDst->Close();rn continue;rn rnrn //tempList.clear();rn pDst->First();rn while(!pDst->Eof)rn rn AMQData data;rn data.m_QuotaInfo.m_iCjsl = pDst->FieldByName("Volumn")->AsInteger;rn data.m_QuotaInfo.m_fCjje = pDst->FieldByName("Amount")->AsFloat;rn strcpy(data.m_szHqrq, pDst->FieldByName("QuotaDate")->AsString.t_str());rn strcpy(data.m_QuotaInfo.m_szHqsj, pDst->FieldByName("QuotaTime")->AsString.t_str());rn strcpy(data.m_QuotaInfo.m_szZqdm, strTableName.SubString(6, 6).t_str());rn data.m_QuotaInfo.m_cJysdm = strTableName[5];rn if (data.m_QuotaInfo.m_iCjsl == 0) rn data.m_QuotaInfo.m_fZjcj = pDst->FieldByName("Close")->AsFloat;rn else rn data.m_QuotaInfo.m_fZjcj = data.m_QuotaInfo.m_fCjje / data.m_QuotaInfo.m_iCjsl;rn rnrn //data.SetTDateTime();rnrn g_stGlobalVar.tempList.push_back(data);rn pDst->Next();rnrn dwCurrCount++;rn rnrn g_stGlobalVar.m_lstQuotaList[g_stGlobalVar.m_nProd].merge(g_stGlobalVar.tempList);rn rnrn if(dwCurrCount > 0)rn rn dwTotalCount += dwCurrCount;rnrn //this->m_etErrorLevel = xelPrompt;rn m_strErrorMsg.sprintf("[历史数据缓冲线程]缓冲成功(%d/%d).", dwCurrCount, dwTotalCount);rn Synchronize(&AddErrorMsg);rn rnrn // set dtCurProdDt to next week;rn dtCurProdDt = weekEnd;rnrn // set m_nProd to the other buffer (list)rn int index = g_stGlobalVar.m_nProd;rn g_stGlobalVar.m_nProd = !g_stGlobalVar.m_nProd;rnrn // release producer buffer's mutexrn ReleaseMutex(g_stGlobalVar.m_hMutex[index]);rnrn m_strErrorMsg.sprintf("Producer: Releasing mutex[%d]", index);rn Synchronize(&AddErrorMsg);rnrn //MySleep(100);rn rn rn[/code]rnrn我已经调试了一天了都没找到哪里出错了。望哪位大侠帮忙看一看。。。谢谢啦! 论坛

没有更多推荐了,返回首页