TThread和继承

tsm*_*smr 1 delphi oop inheritance multithreading

我很难从基本的TThread类实现多层继承.
基于我对OOP的了解应该是可能的,但也许它不能应用于线程.
我的目标是使用TMyBaseThread来实现后代类通用的所有代码.这是我尝试过的:

TMyBaseThread = class(TThread)
private
    procedure BuildBaseObjects(aParam : TParam);
    procedure Execute; virtual; abstract;
protected
    constructor Create(Param : TParam); reintroduce; virtual;
end;

TMyFileThread = class(TMyBaseThread)
private
    procedure Execute; reintroduce;
public
    constructor Create(OtherParam : TOtherParam); reintroduce; overload;
end;

TMyDBThread = class(TMyBaseThread)
private
    procedure Execute; reintroduce;
public
    constructor Create(aDB : TDatabase); reintroduce; overload;
end;


implementation


constructor TMyBaseThread.Create(Param : TParam);
begin
    inherited Create(False);
    Self.BuildBaseObjects(Param);
    [do some stuff]
end;

constructor TMyFileThread.Create(OtherParam : TOtherParam);
var
    param : TParam;
begin
    inherited Create(param);
    [do some other stuff]
end;

procedure TMyFileThread.Execute;
begin
    while not Terminated do
        doWork(); <-- this is never called
end;


constructor TMyDBThread.Create(aDB : TDatabase);
var
    param : TParam;
begin
    inherited Create(param);
end;

procedure TMyDBThread.Execute;
begin
    while not Terminated do
        doDatabaseWork(); <-- this is never called
end;
Run Code Online (Sandbox Code Playgroud)

我在TThread的实现中看到,在AfterConstruction中自动调用Executed方法,但是如何让它指向派生类中声明的方法呢?

谢谢!

qua*_*oft 12

首先,我不能支持更多Craig关于使用组合而不是继承来实现通用功能的评论.

尽管架构选择存在疑问,但从您的示例中可以学到很多东西.

在继承类之前,您应该研究要继承的父类的接口.要做到这一点,您可以在源代码的接口部分查找类定义或查找相关文档 - System.Classes.TThread.

看来你已经阅读了文档,所以让我们看一下除了类定义之外的一个TThread:

TThread = class
  private
    ...
  protected
    procedure CheckThreadError(ErrCode: Integer); overload;
    procedure CheckThreadError(Success: Boolean); overload;
    procedure DoTerminate; virtual;
    procedure Execute; virtual; abstract;
    procedure Queue(AMethod: TThreadMethod); overload;
    procedure Synchronize(AMethod: TThreadMethod); overload;
    property ReturnValue: Integer read FReturnValue write FReturnValue;
    property Terminated: Boolean read FTerminated;
  public
    constructor Create(CreateSuspended: Boolean);
    destructor Destroy; override;
    procedure AfterConstruction; override;
    procedure Resume;
    procedure Suspend;
    procedure Terminate;
    function WaitFor: LongWord;
    class procedure Queue(AThread: TThread; AMethod: TThreadMethod); overload;
    class procedure RemoveQueuedEvents(AThread: TThread; AMethod: TThreadMethod);
    class procedure StaticQueue(AThread: TThread; AMethod: TThreadMethod);
    class procedure Synchronize(AThread: TThread; AMethod: TThreadMethod); overload;
    class procedure StaticSynchronize(AThread: TThread; AMethod: TThreadMethod);
    property FatalException: TObject read FFatalException;
    property FreeOnTerminate: Boolean read FFreeOnTerminate write FFreeOnTerminate;
    property Handle: THandle read FHandle;
    property Priority: TThreadPriority read GetPriority write SetPriority;
    property Suspended: Boolean read FSuspended write SetSuspended;
    property ThreadID: THandle read FThreadID;
    property OnTerminate: TNotifyEvent read FOnTerminate write FOnTerminate;
  end;
Run Code Online (Sandbox Code Playgroud)

首先,忽略private该类部分中的任何内容.如果这些字段和方法被标记为private,我们不应该在后代类中使用它们.

然后,寻找任何abstract方法.抽象方法的实现留给了后代类.所以这些是您希望在代码中实现的方法.抽象方法通常使用父类中的一种方法间接调用.

在您的情况下,TThread该类只有一个抽象方法:

procedure Execute; virtual; abstract;
Run Code Online (Sandbox Code Playgroud)

文档说你需要

通过插入执行线程时应执行的代码来定义线程对象的Execute方法.

该文件听起来有点模糊,但这样做的正确方法是这是真的"覆盖"在接口中的方法,而不是"重新引入"吧:

  TMyFileThread = class(TMyBaseThread)
  ...
  protected
      procedure Execute; override;
  ...
Run Code Online (Sandbox Code Playgroud)

然后在实现中实现它:

procedure TMyFileThread.Execute;
begin
    while not Terminated do
      Sleep(1); // do some other stuff
end;
Run Code Online (Sandbox Code Playgroud)

您可能会注意到我们如何Execute在该protected部分中声明了方法的覆盖定义.这是必需的,因为父类中方法的定义也在该protected部分中,因此我们只能在具有更高可见性(protectedpublic)的部分中覆盖它.

覆盖方法时很少需要增加可见性,因此我们只保持相同的可见性.

我们使用override关键字告诉基类使用该方法的变体而不是它自己的变体.如果你错过了override关键字,那么Execute根本不会调用该方法,并且基类会尝试调用它自己的Execute方法(如果有的话).

另外需要注意的是,您不需要Execute在基类中重新声明该方法,因为您没有在那里实现它.这就是为什么你应该删除以下定义:

TMyBaseThread = class(TThread)
...
    //procedure Execute; virtual; abstract;  <- remove this
...
Run Code Online (Sandbox Code Playgroud)

execute方法已在TThread类中定义.

现在,让我们看一下构造函数.基类有一个常规构造函数既不是virtual,也不是dynamic:

  public
    constructor Create(CreateSuspended: Boolean);
Run Code Online (Sandbox Code Playgroud)

这意味着你不能覆盖那些构造函数,如果你想在对象创建时添加额外的逻辑,你应该创建自己的构造函数来包装它们.

正确的方法是只使用一组不同的参数声明一个构造函数,而不重新引入,重载或覆盖基类:

public
      //constructor Create(Param : TParam); reintroduce; virtual;
      constructor Create(Param : TParam);
Run Code Online (Sandbox Code Playgroud)

此外,请记住,构造函数应该几乎总是在该public部分中.

您也不需要创建构造函数virtual.如果你的TMyFileThreadTMyDBThread类需要在构造函数中添加一些逻辑而不更改构造函数参数,则可以这样做.

当您更改参数集时,只需要将继承的构造函数称为新构造函数中的第一件事:

constructor TMyFileThread.Create(OtherParam : TOtherParam);
var
    param : TParam;
begin
    inherited Create(param); // It is enough to call the base constructor at the top
    // do some other stuff
end;
Run Code Online (Sandbox Code Playgroud)

该定义不需要关键字:

  TMyFileThread = class(TMyBaseThread)
  ...
  public
      constructor Create(OtherParam : TOtherParam);
Run Code Online (Sandbox Code Playgroud)

您是否注意到我们inherited Create(param)以前如何调用基础构造函数,但我们没有使用inherited Execute;?那是因为该Execute方法被标记为abstract并且在基类中没有默认实现.在抽象方法上使用inherited会导致异常,因为没有默认方法可以调用.

作为一般规则调用inherited Create必须的,如果你调用基类的构造被标记为virtual,但几乎总是需要,即使它没有标记等等.

最后,我想制定相关关键字及其最常见用途的摘要:

  • virtual - 声明一个可以在后代类中重新实现的方法.那是后代类可以改变方法的行为.他们可以用自己的代码替换其中的所有代码,或者可以调用基本方法,然后在之前/之后添加其他行为.此关键字仅在首次定义方法的基类中使用.后代类使用其他关键字.
  • 动态 - 认为它是一样的virtual.可以节省一些内存资源,但前提是该方法未在所有后代类中重新实现,并且有许多对象是从这些类创建的.很少用.
  • override - 声明一个方法,该方法通过提供替换基类中的默认代码来重新实现基类.您在后代类中使用override.您可以使用实现中的inherited关键字来调用基本方法.
  • overload - 使用另一组参数声明方法的替代变体.请注意,重载方法与继承无关.当基类调用原始方法时,不会执行该方法的重载版本.通常重载方法规范化参数(将它们转换为其他类型,为某些参数添加默认值),然后调用其他重载方法之一.同样,这无关与继承和virtual,override关键字.虽然有时你可以结合两种效果.
  • 重新引入 - 这又不是继承.您在后代类中使用此关键字.目的是从类内部调用方法(并且只调用类,而不是来自基类的调用)来执行方法的重新引入版本而不是基类.来自基类的调用仍然执行原始版本.很少用.

话虽如此,这是我对您的代码的解释:

interface

uses
  Classes, SysUtils;

type
  TParam = class
  end;

  TOtherParam = class
  end;

  TDatabase = class
  end;

  TMyBaseThread = class(TThread)
  private
      procedure BuildBaseObjects(aParam : TParam);
  protected
  public
      constructor Create(Param : TParam);
  end;

  TMyFileThread = class(TMyBaseThread)
  private
  protected
      procedure Execute; override;
  public
      constructor Create(OtherParam : TOtherParam);
  end;

  TMyDBThread = class(TMyBaseThread)
  private
  protected
      procedure Execute; override;
  public
      constructor Create(aDB : TDatabase);
  end;

implementation

{ TMyBaseThread }

constructor TMyBaseThread.Create(Param : TParam);
begin
    inherited Create(False);
    Self.BuildBaseObjects(Param);
    // Do some stuff
end;

procedure TMyBaseThread.BuildBaseObjects(aParam : TParam);
begin
    // Do some stuff
end;

{ TMyFileThread }

constructor TMyFileThread.Create(OtherParam : TOtherParam);
var
    param : TParam;
begin
    inherited Create(param); // Remember to initialize param somehow
    // Do some other stuff
end;

procedure TMyFileThread.Execute;
begin
    while not Terminated do
      Sleep(1);
end;

{ TMyDBThread }

constructor TMyDBThread.Create(aDB : TDatabase);
var
    param : TParam;
begin
    inherited Create(param); // Remember to initialize param somehow
end;

procedure TMyDBThread.Execute;
begin
    while not Terminated do
        Sleep(1);
end;
Run Code Online (Sandbox Code Playgroud)

PS.实际上,使用继承对TThread插件体系结构或任务工作者来说非常有用.您可以查看相关示例.