第三节:设计一个有生命力的工作线程
 
创建一个线程,用完即扔。相信很多初学者都曾这样使用过。
频繁创建释放线程,会浪费大量资源的,不科学。
 
1.如何让多线程能多次被复用?
 
关键是不让代码退出 Execute 这个函数,一旦退出此函数,此线程的生命周期即结束。
要做到这一点,就需要在 Execute 中写一个”死循环“。大致如下:
 
procedure TFooThread.Execute;
begin
  // 0.挂起
  while not Terminated do // Terminated 是 TThread 的一个 Boolean 属性。
  begin
    // 1.获得参数
    // 2.计算
    // 3.返回结果
    // 4.挂起
  end;
end;
 
原本 TThread 是有挂起功能这个函数的,叫 suspend,但是在 XE2 后,已经废止此函数。
故需要找一个替代品 TEvent ,此类在 System.SyncObjs 单元中。于是:
 
unit uFooThread;
interface
uses
  System.Classes, System.SyncObjs;
type
 
  TFooThread = class(TThread)
  private
    FEvent: TEvent; // 此类用来实现线程挂起功能
  protected
    procedure Execute; override;
  public
    constructor Create(CreateSuspended: Boolean);
    destructor Destroy; override;
    procedure StartThread; // 设计线程的启动函数。
  end;
 
implementation
 
constructor TFooThread.Create(CreateSuspended: Boolean);
begin
  inherited;
  FEvent := TEvent.Create(niltruefalse'');// 默认让 FEvent 无信号
  FreeOnTerminate := true// True的意义是,代码退出 Execute 后,本类自动释放。
end;
 
destructor TFooThread.Destroy;
begin
  FEvent.Free;
  inherited;
end;
 
procedure TFooThread.Execute;
begin
  FEvent.WaitFor;
  // 如果 FEvent 无信号,就一直等。
  // 如果 FEvent 已被设置有信号,就退出 WaitFor ,解除阻塞。
  // 这样,就实现了线程的挂起功能。
  // 挂起是指线程时空的代码,停在了当前位置,就是 FEvent.WaitFor 这个位置。
  FEvent.ResetEvent; // 清除信号,以便下一次继续挂起。
  while not Terminated do
  begin
    // 1.获得参数
    // 2.计算
    // 3.返回结果
    FEvent.WaitFor; // 同上
    FEvent.ResetEvent;
  end;
end;
 
procedure TFooThread.StartThread;
begin
  FEvent.SetEvent;
  // 所谓启动线程功能,就是要让 FEvent 有信号,让它解除阻塞。
end;
end.
以上代码已实现一个有生命力的线程。
 
2. 如何正常退出线程?
 
必须正视这个问题,线程代码必须要有正常的退出方式,切不可用 KillThread 等暴力方法。
 
// 线程正常退出示例
var
  foo: TFooThread;
begin
  foo := TFooThread.Create(false); // false 是指创建后不挂起,直接运行 Execute 中的代码。
  sleep(1000); // 技术性代码,请忽略,但此处又不可少。
  foo.Terminate; // 此句的功能是 Terminated:=True;
  // Terminated TThread 的一个 Boolean 属性
  // 在 Execute 函数中我们用它做为退出循环的标志
  // 请学习系统源码中的英语命名的方法,注意词性,时态。
  // Terminate 是动词,是一个函数。而 Terminated 是过去分词,是一个属性。
  foo.StartThread; // 启动线程。
  // FreeOnTerminated 已在 Create 函数中设置为 True 。
  // 所以,代码退出 Execute 后,foo 会自动 free 的。
end;
 
3.线程复用示例
 
unit uFooThread; // 用于计算的线程类
interface
uses
  System.Classes, System.SyncObjs;
 
type
  TFooThread = class;
  TOnWorked = procedure(Sender: TFooThread) of object;
  TFooThread = class(TThread)
  private
    FEvent: TEvent;
  protected
    procedure Execute; override;
  public
    constructor Create(CreateSuspended: Boolean);
    destructor Destroy; override;
    procedure StartThread;
  public
    Num: integer;
    Total: integer;
    OnWorked: TOnWorked;
  end;
 
implementation
 
constructor TFooThread.Create(CreateSuspended: Boolean);
begin
  inherited;
  FEvent := TEvent.Create(niltruefalse'');
  FreeOnTerminate := true;
end;
 
destructor TFooThread.Destroy;
begin
  FEvent.Free;
  inherited;
end;
 
procedure TFooThread.Execute;
var
  i: integer;
begin
  FEvent.WaitFor;
  FEvent.ResetEvent;
  while not Terminated do
  begin
    Total := 0;
 
    if Num > 0 then
    begin
      for i := 1 to Num do
      begin
        Total := Total + i;
        sleep(10);//故意让线程耗时,以达到更好的演示效果。
      end;
    end;
 
    if Assigned(OnWorked) then
      OnWorked(self);
 
    FEvent.WaitFor;
    FEvent.ResetEvent;
  end;
end;
 
procedure TFooThread.StartThread;
begin
  FEvent.SetEvent;
end;
 
end.
 
unit Unit11; //在窗口中调用
interface
uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, uFooThread;
 
type
  TForm11 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    Edit1: TEdit;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
    FooThread: TFooThread;
    procedure OnWorked(Sender: TFooThread); // 用来接收线程的 OnWorked 事件。
    // 取名是任意的,只要参数相同。
    // 如果你也可以取名为 procedure OnFininshed (O:TFooThread); 同样有效。
  public
    { Public declarations }
  end;
 
var
  Form11: TForm11;
 
implementation
{$R *.dfm}
// 本例为了照顾初学者,未对控件取正确的名字。
// 以后的章节中,将全部采用合理的命名,且提供源码下载地址。
procedure TForm11.Button1Click(Sender: TObject);
var
  n: integer;
begin
  Button1.Enabled := false// 禁用此 button ,以防线程运行期间误点
  // 这是很重要的!用了线程,就要对所有的情况负责。
  // button 被禁用后,在线程计算完成的事件中,将恢复
  // 就可以继续点击它了。
  n := StrToIntDef(Edit1.Text, 0);
  FooThread.Num := n;
  FooThread.StartThread;
end;
 
procedure TForm11.FormCreate(Sender: TObject);
begin
  FooThread := TFooThread.Create(false);
  FooThread.OnWorked := self.OnWorked;
  // 如果按另一个定义的名字也可以写:
  // FooThread.OnWorked:=self.OnFininished;
end;
 
procedure TForm11.FormDestroy(Sender: TObject);
begin
  FooThread.Terminate;
  FooThread.StartThread;
  // 释放线程。此处不是很严谨,以后章节的代码中将完善它。
end;
 
procedure TForm11.OnWorked(Sender: TFooThread);
var
  s: string;
begin
  s := IntToStr(Sender.Num);
  s := s + '的累加和为:';
  s := s + IntToStr(Sender.Total);
  // 此处是线程时空,操作 UI 要用 Synchronize;
  TThread.Synchronize(nil,
    procedure
    begin
      Memo1.Lines.Add(s); //匿名函数,可以用外部的变量(s)。这是很高级的特性。
      Button1.Enabled := true//恢复按钮,以便继续使用。
    end);
  // 此处还可以作为继续启动线程的入口,下一章节会讲解。
end;
 
end.
 
至此,一个完整的可复用的线程已基本完成。下一节讲解,如何将此线程设计得更为通用和严谨。
 

delphi 线程教学第三节:设计一个有生命力的工作线程的更多相关文章

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

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

  2. WPF 多线程 UI:设计一个异步加载 UI 的容器

    对于 WPF 程序,如果你有某一个 UI 控件非常复杂,很有可能会卡住主 UI,给用户软件很卡的感受.但如果此时能有一个加载动画,那么就不会感受到那么卡顿了.UI 的卡住不同于 IO 操作或者密集的 ...

  3. 一个使用高并发高线程数 Server 使用异步数据库客户端造成的超时问题

    现象 今天在做一个项目时, 将 tomcat 的 maxThreads 加大, 加到了 1024, tomcat 提供的服务主要是做一些运算, 然后插入 redis, 查询 redis, 最后将任务返 ...

  4. 使用C++11实现一个半同步半异步线程池

    前言 C++11之前我们使用线程需要系统提供API.posix线程库或者使用boost提供的线程库,C++11后就加入了跨平台的线程类std::thread,线程同步相关类std::mutex.std ...

  5. 简单分析ThreadPoolExecutor回收工作线程的原理

    最近阅读了JDK线程池ThreadPoolExecutor的源码,对线程池执行任务的流程有了大体了解,实际上这个流程也十分通俗易懂,就不再赘述了,别人写的比我好多了. 不过,我倒是对线程池是如何回收工 ...

  6. 分布式缓存系统 Memcached 工作线程初始化

    Memcached采用典型的Master-Worker模式,其核心思想是:有Master和Worker两类进程(线程)协同工作,Master进程负责接收和分配任务,Worker进程负责处理子任务.当各 ...

  7. JAVA之工作线程数究竟要设置多少

    一.需求缘起 Web-Server通常有个配置,最大工作线程数,后端服务一般也有个配置,工作线程池的线程数量,这个线程数的配置不同的业务架构师有不同的经验值,有些业务设置为CPU核数的2倍,有些业务设 ...

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

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

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

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

随机推荐

  1. linux环境下redis安装

    本篇文章主要说明的是Linux环境下redis数据库的安装: 首先进入目标目录: 下载安装包,执行命令: wget http://download.redis.io/releases/redis-4. ...

  2. Intel IPP 图像空间转换

    一. 背景 用QuickSync VPP模块做RGBA到NV12的颜色空间转换导致文字显示蒙上一层颜色的问题, 暂时怀疑是VPP自身的问题,因为参数设置都是按官方demo设置的.所以尝试使用IPP来做 ...

  3. 【TFS】解决TFS编译中文乱码问题

    前言; TFS2018做程序集成非常方便,线上编译然后直接生成docker镜像,但是在使用过程中遇到编译窗口中文乱码的问题,这个问题找了好久没人知道怎么解决.如下: 这个问题不解决,每次编译失败,研发 ...

  4. cloudera impala编译 安装 配置 启动

    无论是采用GDB调试impala或者尝试修改impala源码,前提都是需要本地环境编译impala,这篇文章详细的分享一下impala编译方法以及编译过程遇到的棘手的问题: 前言: impala官方的 ...

  5. curl使用说明

    默认curl使用get请求,可以使用-d方式指定使用post方式传递数据 https://www.cnblogs.com/gbyukg/p/3326825.html

  6. Python3 函数 一

    什么是函数? 函数一词来源于数学,但编程中的「函数」概念,与数学中的函数是有很大不同的,编程中的函数在英文中也有很多不同的叫法.在BASIC中叫做subroutine(子过程或子程序),在Pascal ...

  7. Render树、RenderObject与RenderLayer

    Chapter: 呈现树的构建 1. 呈现树与CSS盒子模型千丝万缕的关系 2. 呈现树与DOM树的关系 3. 浏览器构建呈现树的流程 4. Firefox的规则树和样式上下文树 5. 规则树是如何解 ...

  8. 关于ansible变量的一个问题

    ansible-playbook 使用with_items 时 items中 如果有变量 {} 外面可以用 “” items中 如果都是固定值,没有用到变量,{}最外面不要加 “” ,不然报错,mmp

  9. Linux学习之CentOS(一)--CentOS6.6下Mysql数据库的安装与配置

    在这里我是通过yum来进行mysql数据库的安装的,通过这种方式进行安装,可以将跟mysql相关的一些服务.jar包都给我们安装好,所以省去了很多不必要的麻烦!!! [root@larry ~]# c ...

  10. 测试CPU核心个数

    //测试CPU核心个数 #if !defined (_WIN32) && !defined (_WIN64) #define LINUX #include <unistd.h&g ...