【问题标题】:Resuming suspended thread in Delphi 2010?在 Delphi 2010 中恢复挂起的线程?
【发布时间】:2009-10-26 10:26:41
【问题描述】:

TThread 的 resume 方法在 D2010 中已弃用。所以,我认为它现在应该像这样工作:

TMyThread = class (TThread)
protected
  Execute; override;
public
  constructor Create;
end;
...

TMyThread.Create;
begin
  inherited Create (True);
  ...
  Start;
 end;

不幸的是,我收到一个异常“无法在正在运行或暂停的线程上调用启动”...考虑到文档告诉我应该在以暂停模式创建的线程上调用 Start 的事实,这对我来说似乎很奇怪。

我在这里错过了什么?

【问题讨论】:

    标签: multithreading delphi delphi-2010


    【解决方案1】:

    The reason is that a Thread is not supposed to start itself.

    线程永远不知道初始化何时完成。构造与初始化不同(构造应始终简短且无异常;构造后进行进一步的初始化)。

    类似的情况是 TDataSet:任何 TDataSet 构造函数都不应该调用 Open,或设置 Active := True强>.

    另见blog entry by Wings of Wind

    您应该:

    • 通过调用 Create(true) 创建暂停的 TMyThread 并在 TMyThread 类之外执行 Start
    • 创建非挂起的 TMyThread,确保 Create 构造函数进行完全初始化,并让TThread.AfterConstruction 启动线程。

    TThread使用说明

    基本上,线程应该就是:对执行代码的上下文的封装。

    实际执行的代码(业务逻辑)应该在其他类中。

    通过将这两者解耦,您可以获得很大的灵活性,尤其是从多个位置启动业务逻辑(这在编写单元测试时非常方便!)。

    这是您可以使用的那种框架:

    unit DecoupledThreadUnit;
    
    interface
    
    uses
      Classes;
    
    type
      TDecoupledThread = class(TThread)
      strict protected
        //1 called in the context of the thread
        procedure DoExecute; virtual;
        //1 Called in the context of the creating thread (before context of the new thread actualy lives)
        procedure DoSetUp; virtual;
        //1 called in the context of the thread right after OnTerminate, but before the thread actually dies
        procedure DoTearDown; virtual;
      protected
        procedure DoTerminate; override;
        procedure Execute; override;
      public
        constructor Create;
        procedure AfterConstruction; override;
      end;
    
    implementation
    
    constructor TDecoupledThread.Create;
    begin
      // create suspended, so that AfterConstruction can call DoSetup();
      inherited Create(True);
    end;
    
    procedure TDecoupledThread.AfterConstruction;
    begin
      // DoSetUp() needs to be called without the new thread in suspended state
      DoSetUp();
      // this will unsuspend the underlying thread
      inherited AfterConstruction;
    end;
    
    procedure TDecoupledThread.DoExecute;
    begin
    end;
    
    procedure TDecoupledThread.DoSetUp;
    begin
    end;
    
    procedure TDecoupledThread.DoTearDown;
    begin
    end;
    
    procedure TDecoupledThread.DoTerminate;
    begin
      inherited DoTerminate();
      // call DoTearDown on in the thread context right before it dies:
      DoTearDown();
    end;
    
    procedure TDecoupledThread.Execute;
    begin
      // call DoExecute on in the thread context
      DoExecute();
    end;
    
    end.
    

    您甚至可以通过以下方式使其基于事件:

    unit EventedThreadUnit;
    
    interface
    
    uses
      Classes,
      DecoupledThreadUnit;
    
    type
      TCustomEventedThread = class(TDecoupledThread)
      private
        FOnExecute: TNotifyEvent;
        FOnSetUp: TNotifyEvent;
        FOnTearDown: TNotifyEvent;
      strict protected
        procedure DoExecute; override;
        procedure DoSetUp; override;
        procedure DoTearDown; override;
      public
        property OnExecute: TNotifyEvent read FOnExecute write FOnExecute;
        property OnSetUp: TNotifyEvent read FOnSetUp write FOnSetUp;
        property OnTearDown: TNotifyEvent read FOnTearDown write FOnTearDown;
      end;
    
      // in case you want to use RTTI
      TEventedThread = class(TCustomEventedThread)
      published
        property OnExecute;
        property OnSetUp;
        property OnTearDown;
      end;
    
    implementation
    
    { TCustomEventedThread }
    
    procedure TCustomEventedThread.DoExecute;
    var
      TheOnExecute: TNotifyEvent;
    begin
      inherited;
      TheOnExecute := OnExecute;
      if Assigned(TheOnExecute) then
        TheOnExecute(Self);
    end;
    
    procedure TCustomEventedThread.DoSetUp;
    var
      TheOnSetUp: TNotifyEvent;
    begin
      inherited;
      TheOnSetUp := OnSetUp;
      if Assigned(TheOnSetUp) then
        TheOnSetUp(Self);
    end;
    
    procedure TCustomEventedThread.DoTearDown;
    var
      TheOnTearDown: TNotifyEvent;
    begin
      inherited;
      TheOnTearDown := OnTearDown;
      if Assigned(TheOnTearDown) then
        TheOnTearDown(Self);
    end;
    
    end.
    

    或者像这样适应 DUnit TTestCase 后代:

    unit TestCaseThreadUnit;
    
    interface
    
    uses
      DecoupledThreadUnit,
      TestFramework;
    
    type
      TTestCaseRanEvent = procedure (Sender: TObject; const TestResult: TTestResult) of object;
      TTestCaseThread = class(TDecoupledThread)
      strict private
        FTestCase: TTestCase;
      strict protected
        procedure DoTestCaseRan(const TestResult: TTestResult); virtual;
        function GetTestCase: TTestCase; virtual;
        procedure SetTestCase(const Value: TTestCase); virtual;
      protected
        procedure DoExecute; override;
        procedure DoSetUp; override;
        procedure DoTearDown; override;
      public
        constructor Create(const TestCase: TTestCase);
        property TestCase: TTestCase read GetTestCase write SetTestCase;
      end;
    
    implementation
    
    constructor TTestCaseThread.Create(const TestCase: TTestCase);
    begin
      inherited Create();
      Self.TestCase := TestCase;
    end;
    
    procedure TTestCaseThread.DoExecute;
    var
      TestResult: TTestResult;
    begin
      if Assigned(TestCase) then
      begin
        // this will call SetUp and TearDown on the TestCase
        TestResult := TestCase.Run();
        try
          DoTestCaseRan(TestResult);
        finally
          TestResult.Free;
        end;
      end
      else
        inherited DoExecute();
    end;
    
    procedure TTestCaseThread.DoTestCaseRan(const TestResult: TTestResult);
    begin
    end;
    
    function TTestCaseThread.GetTestCase: TTestCase;
    begin
      Result := FTestCase;
    end;
    
    procedure TTestCaseThread.SetTestCase(const Value: TTestCase);
    begin
      FTestCase := Value;
    end;
    
    procedure TTestCaseThread.DoSetUp;
    begin
      if not Assigned(TestCase) then
        inherited DoSetUp();
    end;
    
    procedure TTestCaseThread.DoTearDown;
    begin
      if not Assigned(TestCase) then
        inherited DoTearDown();
    end;
    
    end.
    

    --杰罗恩

    【讨论】:

    • 所以正确的方法是MyThread := TMyThread.Create 然后MyThread.Start,在Execute 的开头执行所有冗长的初始化?该类的用户应该如何知道线程需要手动启动? (文档除外)
    • 由于假定该类的用户完全精通多线程编程,这应该是一个小细节。不要让多线程类逃跑给那些还没有准备好处理多线程编程所有细微差别的人。
    • 实际上,请参阅我的编辑:有两种方法可以做到这一点。一般来说,线程不应该知道它的初始化,因为这取决于该线程范围之外的因素。我知道大多数线程类都是针对某个问题的。但它们应该分为两部分:只执行“代码”的线程类本身,以及知道应该执行哪些代码以及以什么顺序(初始化、主块、终结)执行的业务类。
    • 该新闻组线程中有 264 条消息。你能总结一下吗?当然,线程应该知道它的初始化,就像任何其他类应该知道它自己的初始化一样。这就是构造函数的用途。在构造函数完成运行时,该类应该完全可以使用了。构造函数引发异常并没有错;该语言是为此设计的。一般来说,线程启动本身绝对没有问题。唯一的危险是在早期版本中,而且很容易解决。 (Uwe 的回答和我的评论。)
    • @Ted:我希望 Embarcadero 论坛服务器有更简单的方法来指向单个消息。我将尝试总结一下:关键是在调用 AfterConstruction 之前线程无法启动。但即使在那个时候,它也不知道初始化是否完成:其他东西可能想要设置一些属性。因此,最好将线程与要运行的业务逻辑分离。然后你可以初始化那个逻辑并将完整的东西传递给你的线程。
    【解决方案2】:

    简答:调用继承的 Create(false) 并省略 Start!

    一个非创建挂起线程的实际开始是在AfterConstruction中完成的,它在所有构造函数都被调用之后被调用。

    【讨论】:

    • 请注意,这是一个相对较新的发展。旧版本真的会在继承的构造函数完成运行后立即开始运行。不过,有一个非常简单的解决方法:调用继承的构造函数last。 (没有理由先调用它;后代很少需要基本 TThread 构造函数设置的任何属性值。)
    • 不,@Jan,继承的构造函数不能随意将您的字段设置为零。它无法访问您的字段,因为它是在这些字段甚至存在之前编写和编译的,因此它无法引用它们。 (如果您正在考虑使用 ZeroMemory 清除自身的构造函数,那在 any 类中将是灾难性的,而不仅仅是 TThread。在 any 构造函数运行之前,所有字段都将归零. 参见 TObject.InitInstance。)
    猜你喜欢
    • 2012-05-20
    • 2011-07-09
    • 2014-06-17
    • 1970-01-01
    • 2011-05-23
    • 2023-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多