一、什么是主线程?什么是工作线程?
在Delphi窗体应用中,线程主要分为两类,职责明确,新手记住"分工"就能分清:
主线程(也叫界面线程) :每个窗体应用程序启动时创建的第一条线程,是程序的"门面担当"。负责做和界面相关的所有事,比如:- 创建窗体、显示按钮/输入框等控件
- 响应你的鼠标点击、键盘输入等windows信息、刷新界面内容等。
- 负责创建新线程
工作线程(后台线程) :除主线程外,我们自己创建的所有线程都是工作线程 ,是程序的"幕后打工人"。负责处理耗时、繁琐的后台任务,比如接收邮件、下载文件、批量处理数据、连接数据库查询等。
核心原则:工作线程不能直接操作界面控件(比如改按钮文字、更改进度条),必须通过特定方式通知主线程处理,否则会导致程序崩溃或界面错乱。
二、Delphi线程基础:必须继承TThread类
Delphi已经帮我们封装好了线程的核心逻辑,我们不需要从零写线程,只要做两件事:
- 从系统自带的
TThread 类继承一个自定义线程类; - 重写(覆盖)TThread类的
Execute方法------这个方法就是工作线程的"核心工作区",里面的代码都会在后台执行。
这里有个关键知识点:只有Execute方法在工作线程中执行,线程类的其他方法(比如构造函数、析构函数、Resume等)都在主线程中执行。
2.1 最简单的Delphi线程类实例
下面是一个可直接参考的基础线程类代码:
bash
unit MyThreadUnit; // 自定义线程单元
interface
uses
Classes; // 必须引入Classes单元,TThread在这里定义
// 自定义线程类,继承自TThread
type
TMyThread = class(TThread)
protected
// 重写Execute方法,工作线程的核心逻辑写在这里
procedure Execute; override;
public
// 构造函数(在主线程执行)
constructor Create(CreateSuspended: Boolean);
end;
implementation
{ TMyThread }
constructor TMyThread.Create(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended); // 调用父类构造函数
// 可选:设置线程结束后自动释放
// FreeOnTerminate := True;
end;
procedure TMyThread.Execute;
begin
// 这里写工作线程的具体任务,以下是示例
Writeln('我是工作线程,正在后台干活!');
// 实际开发中可替换为:接收邮件、处理数据、连接FTP等任务
end;
end.
2.2 Delphi7 专属:TThread 属性和方法(标注兼容性差异)
| 属性/方法 | 作用 | 说明(重点标注 Delphi7 特性) |
|---|---|---|
| Terminated | 终止标志(Delphi7 无公开属性) | 高版本为公开布尔属性,True 表示线程应尽快结束;Delphi7 仅为受保护字段FTerminated,外部无法直接访问,需自定义属性替代 |
Terminate |
终止方法(Delphi7 为静态方法) | 作用是设置内部FTerminated为 True;Delphi7 中无virtual关键字,无法被 override 重写,需自定义方法同步状态 |
Suspended |
暂停状态(Delphi7 公开属性) | True = 暂停(挂起),False = 运行;可直接读取,是 Delphi7 控制线程暂停的核心属性 |
Suspend |
暂停方法(Delphi7 支持) | 暂停线程执行,调用一次挂起计数 + 1;Delphi7 必备,新版本已废弃(替换为 Start) |
Resume |
继续方法(Delphi7 支持) | 唤醒暂停的线程,调用一次挂起计数 - 1;仅当挂起计数 = 0 时线程恢复运行,Delphi7 启动挂起线程的唯一方法 |
FreeOnTerminate |
自动释放(Delphi7 支持) | True = 线程结束后自动释放内存;Delphi7 推荐开启,避免手动释放的繁琐和风险 |
三、线程的创建与运行:控制线程什么时候开始工作
创建自定义线程后,我们可以控制它"立即运行 "或"稍后运行 ",关键在于构造函数的CreateSuspended 参数(布尔值)。
3.1 方式1:创建后立即运行
参数传 False,线程创建完成后马上执行 Execute 方法中的逻辑:
bash
var
myThread: TMyThread;
begin
// 创建线程,参数False=立即运行
myThread := TMyThread.Create(False);
end;
3.2 方式2:创建后先暂停,稍后再运行
参数传 True,线程创建后处于"挂起状态",需要调用 Resume 方法才能启动:
bash
var
myThread: TMyThread;
begin
// 创建线程,参数True=先挂起(不运行)
myThread := TMyThread.Create(True);
// 适当的时刻启动线程(比如点击"开始处理"按钮后)
myThread.Resume;
end;
tips:
- Delphi 7中使用Resume启动线程,新版本Delphi使用Start。
Resume方法在主线程执行,只能唤醒"挂起状态"的线程,不能重复调用(除非线程被多次挂起)。
四、线程的退出:什么时候线程会停止工作?
线程的"生命周期"很简单:当 Execute 方法中的代码执行完毕,工作线程就会正常退出。
4.1 正常退出示例
比如下面的线程,执行完"打印一句话"的逻辑后,线程就结束了:
bash
procedure TMyThread.Execute;
begin
// 执行完这行代码,线程就退出了
Writeln('I am a new thread');
end;
4.2 主动控制退出:用Terminated属性和Terminate方法
如果线程执行的是 "循环任务"(比如持续监听数据、批量处理数据),不能等代码自然执行完,这时候需要主动通知线程退出。由于 Delphi7 无公开的Terminated属性,我们需要通过 "自定义终止标记" 实现,核心逻辑不变(发通知 + 定期检查)。
核心思路
在线程类中定义私有布尔字段FIsTerminated,作为自定义终止标记(替代原生Terminated)。
定义公共方法MarkAsTerminated,用于设置FIsTerminated=True(替代原生Terminate方法),同时调用原生Terminate更新内部FTerminated。
在Execute方法的循环中,定期检查FIsTerminated,收到 "退出通知" 后主动退出循环。
4.2.1 主动退出示例(循环任务场景)
bash
unit MyThreadUnit;
interface
uses
Classes;
type
TMyThread = class(TThread)
private
FIsTerminated: Boolean; // 自定义终止标记(替代Delphi7缺失的Terminated属性)
protected
procedure Execute; override;
public
constructor Create(CreateSuspended: Boolean);
procedure MarkAsTerminated; // 自定义终止方法(替代原生Terminate,Delphi7必备)
property IsTerminated: Boolean read FIsTerminated; // 公开自定义属性,供外部读取
end;
implementation
{ TMyThread }
constructor TMyThread.Create(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
FIsTerminated := False; // 初始化终止标记为"未终止"
FreeOnTerminate := True; // 开启自动释放
end;
// 自定义终止方法(Delphi7专属,替代原生Terminate,无法重写只能自定义)
procedure TMyThread.MarkAsTerminated;
begin
FIsTerminated := True; // 设置自定义终止标记
inherited Terminate; // 调用原生静态方法,更新内部FTerminated(保证线程原生逻辑)
end;
procedure TMyThread.Execute;
begin
// 循环执行后台任务(比如持续接收数据、批量处理数据)
while not FIsTerminated do // 定期检查自定义终止标记(替代原生Terminated)
begin
// 核心任务:接收邮件、处理数据、更新进度等
OutputDebugString('正在后台处理任务...');
Sleep(1000); // 暂停1秒,模拟耗时操作,同时让出CPU(Delphi7避免CPU占用过高)
end;
// 当FIsTerminated为True时,退出循环,Execute执行完毕,线程退出
OutputDebugString('线程收到退出通知,已停止工作');
end;
end.
4.2.2 主线程中通知线程退出(Delphi7 适配,按钮点击事件示例)
bash
// 主窗体中声明线程对象(全局或窗体私有变量)
var
myThread: TMyThread;
// 点击"停止处理"按钮,通知线程退出(Delphi7适配)
procedure TForm1.btnStopClick(Sender: TObject);
begin
if Assigned(myThread) then // 安全判断,避免空对象调用报错(Delphi7必备)
begin
myThread.MarkAsTerminated; // 调用自定义终止方法,发"停工通知"
end;
end;
通俗理解 :MarkAsTerminated方法就像你喊 "打工人下班了",Execute方法里的循环就像 "打工人定期看时间",看到 "下班通知"(FIsTerminated=True)就停止干活,否则继续工作。Delphi7 中只是多了一步 "自定义通知牌",核心逻辑和高版本一致。
五、线程的暂停与唤醒:Suspend和Resume方法
如果需要临时让线程"停工",之后再"复工",就用 Suspend(暂停)和 Resume(唤醒)方法,核心靠"挂起计数"控制:
- 调用 Suspend 一次,挂起计数+1,线程进入"挂起状态"(暂停工作);
- 调用 Resume 一次,挂起计数-1;
- 只有当挂起计数=0时,线程才会恢复运行;计数>0时,线程保持挂起。
5.1 暂停与唤醒示例
bash
var
myThread: TMyThread;
begin
// 创建并启动线程
myThread := TMyThread.Create(False);
// 临时暂停线程(挂起计数=1)
myThread.Suspend;
// 再次暂停(挂起计数=2)
myThread.Suspend;
// 第一次唤醒(挂起计数=1,仍暂停)
myThread.Resume;
// 第二次唤醒(挂起计数=0,恢复运行)
myThread.Resume;
end;
实用场景 :比如线程正在批量处理数据,点击"暂停 "按钮调用 Suspend,点击"继续 "按钮调用 Resume,适合需要手动控制"启停"的场景。
六、线程的销毁:如何释放线程资源?
线程退出后,需要释放它占用的内存资源,否则会导致内存泄漏。有两种销毁方式,根据场景选择:
6.1 自动销毁:设置FreeOnTerminate为True
FreeOnTerminate 是 TThread 的布尔属性,设为 True 后,线程退出(Execute执行完毕)时,系统会自动释放线程对象,不需要我们手动处理。
bash
constructor TMyThread.Create(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
// 设为True,线程退出后自动释放
FreeOnTerminate := True;
end;
适用场景:
- 线程任务简单,不需要在退出后做额外处理(比如不需要获取线程的执行结果、不需要更新最终界面状态);
- 批量创建多个线程,无法逐个手动管理的场景(比如多线程下载文件)。
6.2 手动销毁:用FreeAndNil
如果没有设置 FreeOnTerminate 为 True,就需要我们在主线程中"手动销毁"线程对象。注意 :必须等线程完全退出后再销毁!
bash
var
myThread: TMyThread;
begin
// 创建并启动线程
myThread := TMyThread.Create(False);
// 等待线程退出(比如通过Terminated控制退出后)
// (实际开发中可通过循环判断线程状态,或用OnTerminate事件,后面会讲)
// 手动释放线程资源
FreeAndNil(myThread);
end;
推荐原则:
- 如果线程任务复杂,或需要在退出后获取结果、更新界面,建议手动销毁;
- 简单任务可使用自动销毁,更省心。
七、核心难点:工作线程与主线程通信
前面提到"工作线程不能直接操作界面 ",Delphi7 中该规则更为严格,直接操作界面控件(比如改进度条、改标签文字)会立即导致程序崩溃。此时需要通过 Synchronize 方法实现线程间通信,这是 Delphi7 中最基础、最安全的通信方式。
7.1 先搞懂通信原理
Delphi的线程通信核心逻辑可以简单理解为:
- 工作线程的核心逻辑在
Execute方法中执行,无法直接访问界面控件; - 在线程类中定义 "
成员变量",用于存储需要传递给主线程的数据(比如处理进度、执行结果); - 在线程类中定义 "
无参数同步方法",用于读取成员变量并操作界面控件; - 在 Execute 方法中,调用
Synchronize方法,将同步方法 "切换到主线程执行"; - 线程退出后,可绑定
OnTerminate事件,在主线程中做收尾工作(比如显示 "处理完成")。
7.2 Synchronize方法:让工作线程代码在主线程执行
Synchronize 方法的作用是"同步"------把工作线程中的某个方法,放到主线程中执行。它有个限制:只能传递"无参数的方法 "。
解决参数问题的思路:在线程类中定义"成员变量"存储需要传递的数据,调用 Synchronize 前给变量赋值,在同步方法中读取变量并操作界面。
7.2.1 通信示例:工作线程更新界面进度
bash
unit MyThreadUnit;
interface
uses
Classes, Forms;
type
TMyThread = class(TThread)
private
FProgress: Integer; // 存储进度值(传递给主线程的数据,Delphi7必备成员变量)
FLogMsg: string; // 存储日志信息(传递给主线程的额外数据)
procedure SyncUpdateProgress; // 同步方法(无参数,Delphi7要求)
procedure SyncUpdateLog; // 同步方法(无参数,更新日志界面)
protected
procedure Execute; override;
public
constructor Create(CreateSuspended: Boolean);
end;
implementation
uses
MainForm; // 引入主窗体单元(包含要操作的进度条、富文本框,Delphi7需确保窗体类名一致)
{ TMyThread }
constructor TMyThread.Create(CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
FProgress := 0; // 初始化进度值
FLogMsg := ''; // 初始化日志信息
FreeOnTerminate := True; // 开启自动释放
end;
// 同步方法:更新进度条(在主线程执行,可安全操作界面控件,Delphi7适配)
procedure TMyThread.SyncUpdateProgress;
var
MainFormRef: TForm1; // 明确窗体引用变量,解决Variable required报错(Delphi7必备)
begin
// 安全判断:窗体存在且类型正确
if Assigned(Application.MainForm) and (Application.MainForm is TForm1) then
begin
MainFormRef := TForm1(Application.MainForm);
// 操作主窗体进度条(主线程执行,安全无报错)
MainFormRef.ProgressBar1.Position := FProgress;
end;
end;
// 同步方法:更新日志框(在主线程执行,解决EM_SCROLLCARET未定义报错,Delphi7适配)
procedure TMyThread.SyncUpdateLog;
var
MainFormRef: TForm1;
LogEntry: string;
begin
if Assigned(Application.MainForm) and (Application.MainForm is TForm1) then
begin
MainFormRef := TForm1(Application.MainForm);
// 构造日志条目,包含时间戳
LogEntry := FormatDateTime('yyyy-mm-dd hh:nn:ss', Now()) + ' >>> ' + FLogMsg;
// 追加日志到富文本框
MainFormRef.RichEdit1.Lines.Add(LogEntry);
// 滚动到最新日志
MainFormRef.RichEdit1.SelStart := Length(MainFormRef.RichEdit1.Text);
end;
end;
procedure TMyThread.Execute;
begin
// 初始化日志,同步到主线程界面
FLogMsg := '工作线程启动,开始处理任务...';
Synchronize(SyncUpdateLog); // 传递方法
// 模拟循环处理任务,更新进度
while (FProgress < 100) and not Terminated do // 此处Terminated为内部字段,循环内安全判断
begin
Inc(FProgress); // 更新进度值(每次+1)
FLogMsg := Format('正在处理,当前进度 %d%%', [FProgress]); // 更新日志信息
// 同步到主线程,更新界面(Delphi7中Synchronize会阻塞工作线程,直到主线程处理完毕)
Synchronize(SyncUpdateProgress);
Synchronize(SyncUpdateLog);
Sleep(50); // 暂停50毫秒,模拟耗时操作,避免CPU占用过高(Delphi7必备)
end;
// 任务完成,更新最终日志
FLogMsg := '任务处理完成,最终进度 100%';
Synchronize(SyncUpdateLog);
end;
end.
7.2.2 主窗体代码(MainForm.pas,Delphi7 适配)
bash
delphi
unit MainForm;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls, ExtCtrls;
type
TForm1 = class(TForm)
btnStart: TButton;
ProgressBar1: TProgressBar;
RichEdit1: TRichEdit;
procedure btnStartClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm} // 关联窗体布局文件,Delphi7必备
uses
MyThreadUnit; // 引入自定义线程单元
// 点击"开始处理"按钮,创建并启动线程(Delphi7适配)
procedure TForm1.btnStartClick(Sender: TObject);
var
myThread: TMyThread;
begin
// 初始化界面
ProgressBar1.Position := 0;
RichEdit1.Clear;
RichEdit1.Lines.Add(FormatDateTime('yyyy-mm-dd hh:nn:ss', Now()) + ' >>> 准备启动工作线程...');
// 创建并启动线程(立即运行)
myThread := TMyThread.Create(False);
end;
end.
7.3 OnTerminate事件:线程退出后的通知
OnTerminate 事件在"线程退出后"触发,且自动在主线程执行,适合用来做"收尾工作"(比如显示处理结果、释放资源)。
bash
// 主窗体中使用线程,绑定OnTerminate事件
procedure TMainForm.btnStartClick(Sender: TObject);
var
myThread: TMyThread;
begin
myThread := TMyThread.Create(True);
// 绑定线程退出事件(在主线程执行)
myThread.OnTerminate := ThreadTerminateHandler;
myThread.Resume;
end;
// OnTerminate事件处理函数(主线程执行)
procedure TMainForm.ThreadTerminateHandler(Sender: TObject);
begin
// 显示处理完成
ShowMessage('线程处理完成!');
// 手动释放线程资源(如果没设置FreeOnTerminate)
FreeAndNil(TMyThread(Sender));
end;
八、总结
Delphi多线程核心要点
- 主线程管界面,工作线程管后台任务,Delphi7 中工作线程绝对不能直接操作 VCL 控件,否则立即崩溃;
- 自定义线程必须继承TThread,重写
Execute方法(核心工作区),其他方法均在主线程执行; - 线程创建参数False=立即运行,True=先挂起,Delphi7 中需用Resume唤醒挂起线程;
- 线程退出:Execute执行完自然退出,循环任务用自定义IsTerminated属性控制(弥补 Delphi7 缺失的原生Terminated属性);
- 线程销毁:优先开启FreeOnTerminate=True实现自动销毁,手动销毁需等线程完全退出后用FreeAndNil;
- 线程通信:用Synchronize方法同步无参数过程,通过成员变量传递数据;
最佳实践建议
- 及时检查自定义终止标记:在循环任务中,每次迭代都要检查FIsTerminated,确保线程能快速响应退出通知,避免线程 "卡死";
- 避免长时间占用CPU :在循环中添加
Sleep(哪怕 10 毫秒),让出 CPU 时间片,避免窗体卡死,同时减少系统资源占用; - 资源清理:确保线程结束时释放所有资源
- 异常处理:在线程中捕获异常,避免程序崩溃
- 不要过度创建线程:线程创建和销毁有开销,合理使用线程池
常见问题解答
Q1: 为什么报 "Undeclared identifier: Terminated" 错误?
A1 : Delphi7 中TThread无公开的Terminated属性,只有受保护的FTerminated字段,外部无法直接访问。
解决方案:自定义FIsTerminated字段和IsTerminated属性,替代原生Terminated。
Q2: 为什么线程退出后,窗体还会卡死?
A2: 可能是没有调用Application.ProcessMessages处理界面消息,或Synchronize方法调用过于频繁。
解决方案:在等待线程退出的循环中添加Application.ProcessMessages,减少Synchronize方法的调用频率(比如每更新 10% 进度同步一次界面)。
Q3: 如何在线程间传递数据?
A3: 使用线程类的属性或全局变量,但要注意线程安全。
Q:4 什么时候使用多线程?
A4: 当需要执行耗时操作(如下载文件、处理大量数据)而不希望阻塞界面时。