zoukankan      html  css  js  c++  java
  • delphi 线程教学第三节:设计一个有生命力的工作线程

    第三节:设计一个有生命力的工作线程
     
    创建一个线程,用完即扔。相信很多初学者都曾这样使用过。
    频繁创建释放线程,会浪费大量资源的,不科学。
     
    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.
     
    至此,一个完整的可复用的线程已基本完成。下一节讲解,如何将此线程设计得更为通用和严谨。
     
  • 相关阅读:
    HVIE、HDFS常用操作
    Xshell中使用小键盘问题
    配置SSH免密登录及常见问题
    Linux命令行常用光标控制快捷键
    Linux禁止root用户ssh登录
    sqoop 1.4.7 单机安装
    Hive单机服务的安装配置
    Hadoop 2.9单机安装配置
    CentOS连接wifi
    Servlet
  • 原文地址:https://www.cnblogs.com/lackey/p/6297118.html
Copyright © 2011-2022 走看看