第四节:多线程类的改进
 
1.需要改进的地方
 
a) 让线程类结束时不自动释放,以便符合 delphi 的用法。即 FreeOnTerminate:=false;
b) 改造 Create 的参数,让它适合访问 COM 组件。如:在线程时空中能够创建 TAdoConnection;
c) 设计一个接口能将一个过程( procedure )塞到线程时空中去运行的功能,这样,不必每次重载 Execute 函数。
d) 设计一个输出信息的接口
 
下一节,将讲解如何用多个线程同时执行相同的任务
 
改进后的多线程类
本例源码(delphi xe8版本)下载: FooThread.Zip
 
unit uFooThread;
interface
uses
  System.Classes, System.SyncObjs;
 
type
  TOnMsg = procedure(AMsg: stringof object// 定义一个用于输出信息的事件
 
  // 很多编程资料推荐在 String 参数前面加 const ,以提高效率
  // 我的理由是为了代码美观。如果有多个参数,加上 const 参数太长了。
  // 在以后的使用中,请自己斟酌是否加 const 。
  TFooThread = class(TThread)
  private
    FEvent: TEvent;
    FCanAccessCom: Boolean;
    FRunningInThread: TThreadMethod;
    // TThreadMethod 的定义是 TThreadMethod = Procedure of object;
    // 意为这个 Procedure 是写在一个类中的。
    // 在其它编程语言中,TThreadMethod 被称为函数指针。
    // FRunningInThread 它用来保存将要在线程中运行的代码或 Procedure
    procedure DoExecute;
  protected
    // protected 段中定义的变量与函数,允许在子类中调用。
    procedure Execute; override;
    procedure DoOnStatusMsg(AMsg: string);
    procedure ExecProcInThread(AProc: TThreadMethod);
  public
    constructor Create(ACanAccessCOM: Boolean); reintroduce;
    // reintroduce 是再引入 Create 的参数的意思。
    destructor Destroy; override;
    procedure StartThread; virtual;
  public
    OnStatusMsg: TOnMsg;
    // 亦可改写为 Property OnStatusMsg:TOnMsg Read FOnMsg write SetOnMsg;
    // 太啰嗦了,如果不再对 SetOnMsg 进行操作,建议这样写。
    // 如果后期需要改动,原来的代码亦可以不变。
  end;
  // 未说明之处,请参考面向对象设计基础知识。
 
implementation
 
uses ActiveX, SysUtils;
 
constructor TFooThread.Create(ACanAccessCOM: Boolean);
begin
  inherited Create(false);
  FEvent := TEvent.Create(niltruefalse'');
  FreeOnTerminate := false;
end;
 
destructor TFooThread.Destroy;
begin
  // 此处我们要设计手动 Free 的调用。
  Terminate; // 首先要将 Terminated 设置为 true;
  FEvent.SetEvent; // 启动线程。
  WaitFor; // 此 waitfor 的意思是等待线程退出 Execute
  // 此 WaitFor 是 TThread 类的。注意与 FEvent.WaitFor 区别
  // 本质上,它们都是操作系统提供的信号的等待功能。
  // 有兴趣可以直接参考系统源码 ( delphi 提供的源码 )
  FEvent.Free;
  inherited;
end;
 
procedure TFooThread.DoExecute;
begin
  FEvent.WaitFor;
  FEvent.ResetEvent;
  while not Terminated do
  begin
 
    try
      FRunningInThread; // 因为它是一个 Procedure ,故可直接运行。
    except
      // 捕捉异常,否则异常发生时代码将退出 Execute ,线程生命周期就结束了。
      on e: Exception do
      begin
        DoOnStatusMsg('ThreadErr:' + e.Message);
      end;
    end;
 
    FEvent.WaitFor;
    FEvent.ResetEvent;
 
  end;
end;
 
procedure TFooThread.DoOnStatusMsg(AMsg: string);
begin
  // 这是引发事件常用的写法。
  if Assigned(OnStatusMsg) then
    OnStatusMsg(AMsg);
end;
 
procedure TFooThread.ExecProcInThread(AProc: TThreadMethod);
begin
  FRunningInThread := AProc;
  FEvent.SetEvent; // 启动线程。
  // 需要说明的是,第一次运行本函数 ExecProcInThread 一般是在主线程时空里运行。
  // 第二次运行本函数可以设计为在线程时空中运行,后面章节会讲到。
  // 其作用是把 AProc 塞到线程时空中并启动线程。
end;
 
procedure TFooThread.Execute;
begin
 
  if FCanAccessCom then
  begin
    CoInitialize(nil);
    // 在线程中初始化 COM ,反正调用了此句,才能在线程中使用 COM
    // 这是 windows 操作系统规定的,与 delphi 没有关系。
    // 你用 api 操作线程,在线程中访问 COM 同样需要这样做。
    try
      DoExecute;
    finally
      CoUninitialize; // 与初始化对应,解除线程访问 COM 的能力。
    end;
  end
  else
    DoExecute;
end;
 
procedure TFooThread.StartThread;
begin
end;
 
end.
 
先基于 TFooThread 继承,代码如下。
 
unit uCountThread;
interface
uses
  uFooThread;
type
  TCountThread = class;
  TOnCounted = procedure(Sender: TCountThread) of object;
  TCountThread = class(TFooThread)
  private
    procedure Count;
    procedure DoOnCounted;
  public
    procedure StartThread; override;
  public
    Num: integer;
    Total: integer;
    OnCounted: TOnCounted;
  end;
 
implementation
 
{ TCountThread }
 
procedure TCountThread.Count;
var
  i: integer;
begin
  DoOnStatusMsg('开始计算...');
  Total := 0;
  if Num > 0 then
    for i := 1 to Num do
    begin
      Total := Total + i;
      sleep(10); // 故意变慢,实际代码请删除此行。
      // 实际上为确保线程能够及时退出
      // 此处还应加上一个判断是否出的标志,请大家自行思考。
      // 这又是一个两难的选择。
      // 加了判断标志,退出容易了,但效率又低了。
      // 所以,编程人员总是在效率与友好性中做出选择。
      // 且编且珍惜。
    end;
  DoOnCounted; //引发 OnCounted 事件,告知调用者。
  DoOnStatusMsg('计算完成...');
end;
 
procedure TCountThread.DoOnCounted;
begin
  // if Assigned(OnCounted) then
  // 等价于 if OnCounted <> nil then
  if Assigned(OnCounted) then
    OnCounted(self);
end;
 
procedure TCountThread.StartThread;
begin
  inherited;
  ExecProcInThread(Count); // 把 Count 过程塞到线程中运行。
end;
 
end.
 
是不是简短很多?下面是调用。
 
unit uFrmMain;
 
interface
 
uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, uCountThread;
 
type
  TFrmMain = class(TForm)
    memMsg: TMemo;
    edtNum: TEdit;
    btnWork: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure btnWorkClick(Sender: TObject);
  private
    { Private declarations }
    FCountThread: TCountThread;
    // 取名是一直是个有技术含量的事情。
    // 推荐去掉类名的 T 换成 F 这样的写法。
    procedure DispMsg(AMsg: string);
    procedure OnThreadMsg(AMsg: string);
    procedure OnCounted(Sender: TCountThread);
  public
    { Public declarations }
  end;
 
var
  FrmMain: TFrmMain;
 
implementation
 
{$R *.dfm}
{ TFrmMain }
 
procedure TFrmMain.btnWorkClick(Sender: TObject);
var
  n: integer;
begin
  btnWork.Enabled := false;
  n := StrToIntDef(edtNum.Text, 0);
  FCountThread.Num := n;
  FCountThread.StartThread;
end;
 
procedure TFrmMain.DispMsg(AMsg: string);
begin
  memMsg.Lines.Add(AMsg);
end;
 
procedure TFrmMain.FormCreate(Sender: TObject);
begin
  FCountThread := TCountThread.Create(false); // 此处不需要访问 Com 所以用 false
  FCountThread.OnStatusMsg := self.OnThreadMsg; 
// 因为是在线程时空中引发的消息,故这里用了 OnThreadMsg;
  FCountThread.OnCounted := self.OnCounted;
end;
 
procedure TFrmMain.FormDestroy(Sender: TObject);
begin
  // 这里要注意,尽管我们在 TFooThread 中的析构函数中
  // 写了保证线程退出的函数。那也只是以防万一的。
  // 在线程手动 Free 之前,一定要确保线程代码已经退出了 Execute
 
  // 为了友好退出,又需要在计算代码中加入判断是否退出的标志。
  // 请参考 TCountThread Count 中的注释。
 
  // 本教程一直反复强调“代码退出Execute”这个概念。
  // 用线程,就得负责一切,不可偷懒!
 
  FCountThread.Free;
end;
 
procedure TFrmMain.OnCounted(Sender: TCountThread);
var
  s: string;
begin
  s := IntToStr(Sender.Num) + '累加和为:';
  s := s + IntToStr(Sender.Total);
  OnThreadMsg(s); // 因为这里是线程空间,所以需要用本函数。
  // 而不是 DispMsg;
  // 网络组件,它的数据到达事件,其实是线程时空。要显示信息
  // 也需要 Synchronize; 这是很多初学者易犯的错误。
  // 如果在线程时空中,不用 Synchronize 来操作 UI,就会出现时灵时不灵的状态。
  // 初学者所谓的运行不稳定,调试时又是正常。往往原因就是如此。
 
  TThread.Synchronize(nil,
    procedure
    begin
      btnWork.Enabled := true// 恢复按钮状态。
    end);
 
end;
 
procedure TFrmMain.OnThreadMsg(AMsg: string);
begin
  TThread.Synchronize(nil,
    procedure
    begin
      DispMsg(AMsg);
    end);
end;
 
end.
 
 
 
 
  

delphi 线程教学第四节:多线程类的改进的更多相关文章

  1. delphi 线程教学第六节:TList与泛型

    第六节: TList 与泛型   TList 是一个重要的容器,用途广泛,配合泛型,更是如虎添翼. 我们先来改进一下带泛型的 TList 基类,以便以后使用. 本例源码下载(delphi XE8版本) ...

  2. delphi 线程教学第五节:多个线程同时执行相同的任务

    第五节:多个线程同时执行相同的任务   1.锁   设,有一个房间 X ,X为全局变量,它有两个函数  X.Lock 与 X.UnLock; 有如下代码:   X.Lock;      访问资源 P; ...

  3. delphi 线程教学第七节:在多个线程时空中,把各自的代码塞到一个指定的线程时空运行

    第七节:在多个线程时空中,把各自的代码塞到一个指定的线程时空运行     以 Ado 为例,常见的方法是拖一个 AdoConnection 在窗口上(或 DataModule 中), 再配合 AdoQ ...

  4. delphi 线程教学第一节:初识多线程

    第一节:初识多线程   1.为什么要学习多线程编程?   多线程(多个线程同时运行)编程,亦可称之为异步编程. 有了多线程,主界面才不会因为耗时代码而造成“假死“状态. 有了多线程,才能使多个任务同时 ...

  5. delphi 线程教学第二节:在线程时空中操作界面(UI)

    第二节:在线程时空中操作界面(UI)   1.为什么要用 TThread ?   TThread 基于操作系统的线程函数封装,隐藏了诸多繁琐的细节. 适合于大部分情况多线程任务的实现.这个理由足够了吧 ...

  6. delphi 线程教学第一节:初识多线程(讲的比较浅显),还有三个例子

    http://www.cnblogs.com/lackey/p/6297115.html 几个例子: http://www.cnblogs.com/lackey/p/5371544.html

  7. delphi 线程教学第三节:设计一个有生命力的工作线程

    第三节:设计一个有生命力的工作线程   创建一个线程,用完即扔.相信很多初学者都曾这样使用过. 频繁创建释放线程,会浪费大量资源的,不科学.   1.如何让多线程能多次被复用?   关键是不让代码退出 ...

  8. 多线程的基本概念和Delphi线程对象Tthread介绍

    多线程的基本概念和Delphi线程对象Tthread介绍 作者:xiaoru    WIN 98/NT/2000/XP是个多任务操作系统,也就是:一个进程可以划分为多个线程,每个线程轮流占用CPU运行 ...

  9. Unity多线程(Thread)和主线程(MainThread)交互使用类——Loom工具分享

    Unity多线程(Thread)和主线程(MainThread)交互使用类——Loom工具分享 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com ...

随机推荐

  1. HTML中的上下标标签的演示

    HTML中的上下标标签的演示 #table_head>td { font-weight: bold } tr { text-align: center } 作用 标签 演示代码 呈现效果 上标 ...

  2. 翻译:JVM虚拟机规范1.7中的运行时常量池部分(二)

    本篇为JVM虚拟机规范1.7中的运行时常量池部分系列的第二篇. 4.4.4. The CONSTANT_Integer_info and CONSTANT_Float_info Structures ...

  3. 相同域名不同端口的两个应用,cookie名字、路径都相同的情况下,会覆盖吗

    首先答案是: 会的. 本地测试流程: 两个相同的应用,代码完全相同:只是部署在两个不同的tomcat:域名都是localhost 应用A:部署在http://localhost:8087/ 应用B:部 ...

  4. Java-NIO(八):DatagramChannel

    Java NIO中的DatagramChannel是一个能收发UDP包的通道.操作步骤: 1)打开 DatagramChannel 2)接收/发送数据 同样它也支持NIO的非阻塞模式操作,例如: @T ...

  5. JavaScript push() 方法

    定义和用法: push() :可向数组的末尾添加一个或多个元素,并返回新的长度. 语法 arrayObject.push(newelement1,newelement2,....,newelement ...

  6. Oracle服务启动项

    七个服务的含义分别为: 1. Oracle ORCL VSS Writer Service: Oracle卷映射拷贝写入服务,VSS(Volume Shadow Copy Service)能够让存储基 ...

  7. [LeetCode] Self Dividing Numbers 自整除数字

    A self-dividing number is a number that is divisible by every digit it contains. For example, 128 is ...

  8. [测试题]gene

    Description Input Output Sample Input 3A+00A+A+ 00B+D+A- B-C+00C+ Sample Output bounded Hint 题解 //It ...

  9. 【模板】KMP字符串匹配

    题目描述 如题,给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置. 为了减少骗分的情况,接下来还要输出子串的前缀数组next. (如果你不知道这是什么意思也不要问,去百度 ...

  10. [模版]平衡树splay2

    题目描述 1. 加入:一个新的成员加入同好会,我会分配给他一个没有使用的id,并且询问他的兴趣值val. 2. 修改:id在区间[a,b]内的成员,兴趣值同时改变k,k有可能是负数,表示他们失去了对同 ...