使用什么代替 TThread.Suspend()

What to use instead of TThread.Suspend()

假设我有一个工作线程填充在主线程中声明的一个大向量。虽然工作线程仍然是 运行(响应用户交互),但我希望主线程检查向量是否已填充到一定大小。如果有,我希望它从向量中提取一些值。如果还没有,我希望它等到工作线程填充到所需的大小。

因为工作线程仍然可以向向量添加项目(可能导致 resize/move)我想我只能在工作线程挂起时执行此操作但是 TThread.Suspend( ) 已弃用。我花了几天时间查看 TMutex、TSemaphore 等,但文档很糟糕。谁能指出我正确的方向?

一个可能的解决方案是在工作线程中填充一个单独的较小向量,然后使用同步将其附加到大向量(等等),但我想避免这种情况。

As the worker thread could still be adding items to the vector (possibly resulting in a resize/move) I'm thinking I can only do this while the worker thread is suspended

这是个好主意。

but TThread.Suspend() is deprecated.

即使它没有被弃用,使用它仍然很危险。只有调试器应该挂起线程,这就是 SuspendThread() API 的目的。

I've spent days looking at TMutex, TSemaphore etc. but the documentation is dire. Could anyone point me in the right direction?

您可以简单地用 TCriticalSection or TMutex 包装对 vector 的所有访问,然后主线程和工作线程都可以在需要对 vector 执行任何操作时进入锁。例如:

type
  TMyThread = class(TThread)
  private
    FLock: TCriticalSection;
  protected
    procedure Execute; override;
  public
    constructor Create; reintroduce;
    destructor Destroy; override;
    procedure Lock;
    procedure Unlock;
  end;

constructor TMyThread.Create;
begin
  inherited Create(False);
  FLock := TCriticalSection.Create;
end;

destructor TMyThread.Destroy;
begin
  FLock.Free;
end;

procedure TMyThread.Lock;
begin
  FLock.Enter;
end;

procedure TMyThread.Unlock;
begin
  FLock.Leave;
end;

procedure TMyThread.Execute;
begin
  while not Terminated do
  begin
    Lock;
    try
      // do something...
    finally
      Unlock;
    end;
  end;
end;

MyThread.Lock;
try
  if Vector.Size >= X then
  begin
    // do something ...
  end;
finally
  MyThread.Unlock;
end;

如果您发现工作线程比主线程更多地访问向量,您可以考虑使用 TMultiReadExclusiveWriteSynchronizer or a SRW lock

或者,您可以使用一些 TEvent 对象来通知工作线程何时暂停以及何时恢复。然后主线程可以通知线程暂停并等待它真正暂停,然后访问向量并在完成后取消暂停线程。例如:

type
  TMyThread = class(TThread)
  private
    FPauseEvent: TEvent;
    FPausedEvent: TEvent;
    FResumeEvent: TEvent;
    procedure CheckForPause;
  protected
    procedure Execute; override;
  public
    constructor Create; reintroduce;
    destructor Destroy; override;
    procedure Pause;
    procedure Unpause;
  end;

constructor TMyThread.Create;
begin
  inherited Create(False);
  FPauseEvent := TEvent.Create(nil, True, False, '');
  FPausedEvent := TEvent.Create(nil, True, False, '');
  FResumeEvent := TEvent.Create(nil, True, True, '');
end;

destructor TMyThread.Destroy;
begin
  FPauseEvent.Free;
  FPausedEvent.Free;
  FResumeEvent.Free;
end;

procedure TMyThread.Pause;
begin
  FResumeEvent.ResetEvent;
  FPauseEvent.SetEvent;
  FPausedEvent.WaitFor(Infinite);
end;

procedure TMyThread.Unpause;
begin
  FPauseEvent.ResetEvent;
  FResumeEvent.SetEvent;
end;

procedure TMyThread.CheckForPause;
begin
  if FPauseEvent.WaitFor(0) = wrSignaled then
  begin
    FPausedEvent.SetEvent;
    FResumeEvent.WaitFor(Infinite);
    FPausedEvent.ResetEvent;
  end;
end;

procedure TMyThread.Execute;
begin
  while not Terminated do
  begin
    CheckForPause;
    if Terminated then Exit;
    // do something...
  end;
end;

MyThread.Pause;
try
  if Vector.Size >= X then
  begin
    // do something ...
  end;
finally
  MyThread.Unpause;
end;

下面是我的努力。它避免了 Remy 的 TEvent 的复杂性和 J.. 的最后评论指出的 TCriticalSection 的陷阱。那是假设它有效。看起来确实如此,但如果有人能帮我找出我可能落入的陷阱,我将不胜感激。

用户将看到一个 TForm,其中包含一个名为 VecNdx 的 TEdit,用户可以使用它来输入他想要的向量值的索引和一个名为 GetVecVal 的 TButton,当单击它时,通过打印 VecNdx 的向量值来响应一个名为 VecVal 的 TLabel。

虽然向量值本身是由 rand() 函数生成的,但您可以将它们视为单步执行查询结果集的结果,其中直到最后一步之后才知道大小。

.h file

#ifndef ThreadH
#define ThreadH
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
#include <Vcl.ComCtrls.hpp>
#include <vector>
#include <atomic>
//---------------------------------------------------------------------------
class TMainForm : public TForm
{
__published:    // IDE-managed Components
    TEdit *VecNdx; // user enters vector index
    TButton *GetVecVal; // retreives value for vector at index entered above
    TLabel *VecVal; // displays above value
    void __fastcall GetVecValClick(TObject *Sender);

private:    // User declarations
    class TPopulate : public TThread
    {
    private:
        TMainForm *Main;
        void __fastcall ShowPopulated(void);
        int Count;
        clock_t ThreadStart; // clock() when thread starts running
    protected:
        void __fastcall Execute();
    public:
        __fastcall TPopulate(TMainForm *Parent) : Main(Parent) {}
    } *Populate;

    int VecSize=-1; // updated only after Populate finishes
    std::vector<int> Vec;
    std::atomic<int> UserNdx=-1,UserVal,CountSoFar;

public:     // User declarations
    __fastcall TMainForm(TComponent* Owner);
    __fastcall ~TMainForm();
};
//---------------------------------------------------------------------------
extern PACKAGE TMainForm *MainForm;
//---------------------------------------------------------------------------
#endif





.cpp file

#include <vcl.h>
#pragma hdrstop

#include "Thread.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TMainForm *MainForm;
//---------------------------------------------------------------------------
__fastcall TMainForm::TMainForm(TComponent* Owner) : TForm(Owner)
{
    Populate=new TPopulate(this);
}
//---------------------------------------------------------------------------
__fastcall TMainForm::~TMainForm()
{
    delete Populate;
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::TPopulate::ShowPopulated(void)
{
    Main->Caption = (Terminated ? String("Terminated after ") : String(Count)+" values in ")
    +((clock()-ThreadStart)/CLOCKS_PER_SEC)+" secs";
    Main->VecSize=Count;
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::TPopulate::Execute()
{
    ThreadStart=clock();
    const int Mx=100000000;
    Count=0;
    for (int u; !Terminated && Count<Mx;)
    {
        Main->Vec.push_back(rand() % Mx);
        Count++;
        if ((u = Main->UserNdx) != -1)
        {
            if (Count>u) Main->UserVal=Main->Vec[u];
            else Main->CountSoFar=Count;
            Main->UserNdx=-1;
        }
    }
    Synchronize(ShowPopulated);
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::GetVecValClick(TObject *Sender)
{
    int Ndx=VecNdx->Text.ToIntDef(-1);
    if (Ndx<0 || (VecSize>=0 && Ndx>=VecSize)) throw Exception("Range Error");
    if (VecSize>=0) VecVal->Caption=Vec[Ndx];
    else
    {
        CountSoFar=0; // if Populate changes CountSoFar => Vec[UserNdx] not yet assigned
        UserNdx=Ndx;
        while (UserNdx!=-1); // Ensure Populate processes UserNdx
        VecVal->Caption = CountSoFar ? "Populated only to "+String(CountSoFar) : int(UserVal);
    }
}
//---------------------------------------------------------------------------