简体   繁体   English

使用 C++Builder,如何在主线程中与 WaitFor() 一起运行 AniIndicator?

[英]With C++Builder, how to run AniIndicator along whith WaitFor() in main thread?

Following this Delphi sample and this other one , I built three scenarios in order to run properly TAniIndicator along with WaitFor() in the main thread.这个 Delphi 示例另一个示例之后,我构建了三个场景,以便在主线程中与WaitFor()一起正确运行TAniIndicator

In the first two tests, I use the Synchronize() and Queue() methods with FreeOnTerminate=false/FreeAndNil() .在前两个测试中,我将Synchronize()Queue()方法与FreeOnTerminate=false/FreeAndNil()使用。 In the third test, I got rid of the WaitFor() without success.在第三次测试中,我摆脱了WaitFor()但没有成功。 In all cases, the app freezes with message:在所有情况下,应用程序都会冻结并显示以下消息:

Project1 isn't responding. Project1 没有响应。 Do you want to close it?你想关闭它吗? Wait/OK等等好吗

Note that in the third scenario, the TAniIndicator spun correctly if the variable PHONENumber is assigned before the execution of the REST request.请注意,在第三种情况下,如果在执行 REST 请求之前分配了变量PHONENumber ,则TAniIndicator会正确旋转。 Curiously, after this line, the app freezes again.奇怪的是,在这一行之后,应用程序再次冻结。 This would be my preferred solution.这将是我的首选解决方案。

Below is my code:下面是我的代码:

.h 。H

// ...
//---------------------------------------------------------------------------
class TAlphaThread : public TThread
{private:
 protected:
    void __fastcall Execute();
 public:
    __fastcall TAlphaThread(bool CreateSuspended);
    //void __fastcall OnTerminate(TObject *Sender); Never triggered
};
//---------------------------------------------------------------------------
//===========================================================================
//---------------------------------------------------------------------------
class TBetaThread : public TThread
{private:
 protected:
    void __fastcall Execute();
 public:
    __fastcall TBetaThread(bool CreateSuspended);
    //void __fastcall OnTerminate(TObject *Sender); Never triggered
};
//---------------------------------------------------------------------------
//===========================================================================
//---------------------------------------------------------------------------
class TDeltaThread : public TThread
{private:
 protected:
    void __fastcall Execute();
 public:
    __fastcall TDeltaThread(bool CreateSuspended);
    //void __fastcall OnTerminate(TObject *Sender); Never triggered
};
//---------------------------------------------------------------------------
//===========================================================================
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published:    // Composants gérés par l'EDI
//  ...
private:    // Déclarations utilisateur
public:     // Déclarations utilisateur
    __fastcall TForm1(TComponent* Owner);

    UnicodeString PHONENumber; // Mobile number

    TRESTClient *REST1Client;
    TRESTRequest *REST1Request;
    TRESTResponse *REST1Response;
    TAlphaThread *AlphaThread;
    void __fastcall AlphaThreadTerminated(TObject *Sender);

    TRESTClient *REST2Client;
    TRESTRequest *REST2Request;
    TRESTResponse *REST2Response;
    TBetaThread *BetaThread;
    void __fastcall BetaThreadTerminated(TObject *Sender);

    TRESTClient *REST3Client;
    TRESTRequest *REST3Request;
    TRESTResponse *REST3Response;
    TDeltaThread *DeltaThread;
    void __fastcall DeltaThreadTerminated(TObject *Sender);
};
//---------------------------------------------------------------------------
//...

.cpp .cpp

//...
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{//
 REST1Client = new TRESTClient(this);
 REST1Request = new TRESTRequest(this);
 REST1Response = new TRESTResponse(this);

 REST2Client = new TRESTClient(this);
 REST2Request = new TRESTRequest(this);
 REST2Response = new TRESTResponse(this);

 REST3Client = new TRESTClient(this);
 REST3Request = new TRESTRequest(this);
 REST3Response = new TRESTResponse(this);
}
//---------------------------------------------------------------------------
//===========================================================================
// Scenario 1 => Project1 isn't responding. Do you want to close it ? Wait/OK
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Tap(TObject *Sender, const TPointF &Point)
{//
 PHONENumber = NULL;
 AlphaThread = new TAlphaThread(true);
 AlphaThread->OnTerminate = &AlphaThreadTerminated;
 AlphaThread->Start();

 if(AlphaThread->WaitFor())
  {if(PHONENumber == NULL) ShowMessage(L"Null value!"); else Label1->Text =  PHONENumber;
  }
}
//---------------------------------------------------------------------------
void __fastcall TForm1::AlphaThreadTerminated(TObject * Sender)
{//
 FreeAndNil(Form1->AlphaThread);
}
//---------------------------------------------------------------------------
__fastcall TAlphaThread::TAlphaThread(bool CreateSuspended) : TThread(CreateSuspended)
{//
 FreeOnTerminate = false;
}
//---------------------------------------------------------------------------
/* Never triggered
void __fastcall TAlphaThread::OnTerminate(TObject * Sender)
{ShowMessage(L"TAlphaThread::OnTerminate!");
 FreeAndNil(Form1->AlphaThread);
}*/
//---------------------------------------------------------------------------
void __fastcall TAlphaThread::Execute()
{//
 Synchronize([this](){
   Form1->AniIndicator1->Align = TAlignLayout::Contents;
   Form1->AniIndicator1->Enabled = true;
   Form1->AniIndicator1->Visible = true;
  }
 );

 // Chargement des données
 Form1->REST1Client->BaseURL = "my.URL.php";
 Form1->REST1Request->AddParameter("ID", "123");
 Form1->REST1Request->Client = Form1->REST1Client;
 Form1->REST1Request->Response = Form1->REST1Response;
 Form1->REST1Response->ContentType = "application/json";
 Form1->REST1Request->Execute();

 // Assignation des variables
 TJSONValue *JV0TOPLevel; JV0TOPLevel = Form1->REST1Response->JSONValue;
 TJSONArray *JSONArray = dynamic_cast<TJSONArray*>(JV0TOPLevel);
 TJSONObject *JSONObject = dynamic_cast<TJSONObject*>(JSONArray->Items[0]);
 TJSONValue *JV0ITEMLevel;

 JV0ITEMLevel = JSONObject->GetValue("FIELDName"); Form1->PHONENumber = JV0ITEMLevel->AsType<String>();

 Synchronize([this](){
   Form1->AniIndicator1->Enabled = false;
   Form1->AniIndicator1->Visible = false;
  }
 );

 ReturnValue = 1;
 Terminate();
}
//---------------------------------------------------------------------------
//===========================================================================
// Scenario 2 => Project1 isn't responding. Do you want to close it ? Wait/OK
//---------------------------------------------------------------------------
void __fastcall TForm1::Button2Tap(TObject *Sender, const TPointF &Point)
{//
 PHONENumber = NULL;
 BetaThread = new TBetaThread(true);
 BetaThread->OnTerminate = &BetaThreadTerminated;
 BetaThread->Start();

 if(BetaThread->WaitFor())
  {if(PHONENumber == NULL) ShowMessage(L"Null value!"); else Label2->Text =  PHONENumber;
  }
}
//---------------------------------------------------------------------------
void __fastcall TForm1::BetaThreadTerminated(TObject * Sender)
{//
 FreeAndNil(Form1->BetaThread);
}
//---------------------------------------------------------------------------
__fastcall TBetaThread::TBetaThread(bool CreateSuspended)
    : TThread(CreateSuspended)
{//
 FreeOnTerminate = false;
}
//---------------------------------------------------------------------------
/*
void __fastcall TBetaThread::OnTerminate(TObject * Sender)
{// Never triggered
 ShowMessage(L"TBetaThread::OnTerminate!");
 FreeAndNil(Form1->BetaThread);
}*/
//---------------------------------------------------------------------------
void __fastcall TBetaThread::Execute()
{//
 Queue(NULL, [this](){
   Form1->AniIndicator1->Align = TAlignLayout::Contents;
   Form1->AniIndicator1->Enabled = true;
   Form1->AniIndicator1->Visible = true;
  }
 );

 // Chargement des données
 Form1->REST2Client->BaseURL = "my.URL.php";
 Form1->REST2Request->AddParameter("ID", "123");
 Form1->REST2Request->Client = Form1->REST2Client;
 Form1->REST2Request->Response = Form1->REST2Response;
 Form1->REST2Response->ContentType = "application/json";
 Form1->REST2Request->Execute();

 // Assignation des variables
 TJSONValue *JV0TOPLevel; JV0TOPLevel = Form1->REST2Response->JSONValue;
 TJSONArray *JSONArray = dynamic_cast<TJSONArray*>(JV0TOPLevel);
 TJSONObject *JSONObject = dynamic_cast<TJSONObject*>(JSONArray->Items[0]);
 TJSONValue *JV0ITEMLevel;

 JV0ITEMLevel = JSONObject->GetValue("FIELDName"); Form1->PHONENumber = JV0ITEMLevel->AsType<String>();

 Queue(NULL, [this](){
   Form1->AniIndicator1->Enabled = false;
   Form1->AniIndicator1->Visible = false;
  }
 );

 ReturnValue = 1;
 Terminate();
}
//---------------------------------------------------------------------------
//===========================================================================
// Scenario 3 => Project1 isn't responding. Do you want to close it ? Wait/OK
//---------------------------------------------------------------------------
void __fastcall TForm1::Button3Tap(TObject *Sender, const TPointF &Point)
{//
 PHONENumber = NULL;
 DeltaThread = new TDeltaThread(true);
 /*DeltaThread->OnTerminate = &DeltaThreadTerminated;*/
 DeltaThread->Start();

 while(PHONENumber == NULL) {
  [this]()->String{return PHONENumber;};
 }
 if(PHONENumber == NULL) ShowMessage(L"Null value!"); else Label3->Text =  PHONENumber;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::DeltaThreadTerminated(TObject * Sender)
{//
 FreeAndNil(Form1->DeltaThread);
}
//---------------------------------------------------------------------------
__fastcall TDeltaThread::TDeltaThread(bool CreateSuspended)
    : TThread(CreateSuspended)
{//
 FreeOnTerminate = true;
}
//---------------------------------------------------------------------------
/* //Never triggered
void __fastcall TDeltaThread::OnTerminate(TObject * Sender)
{//ShowMessage(L"OnTerminate!");
 //FreeAndNil(Form1->DeltaThread);
} */
//---------------------------------------------------------------------------
void __fastcall TDeltaThread::Execute()
{//
 //Form1->PHONENumber = 123; Working!!

 Queue(NULL, [this](){
   Form1->AniIndicator1->Align = TAlignLayout::Contents;
   Form1->AniIndicator1->Enabled = true;
   Form1->AniIndicator1->Visible = true;
  }
 );

 //Form1->PHONENumber = 123; Working!!

 // Chargement des données
 Form1->REST3Client->BaseURL = "my.URL.php";
 Form1->REST3Request->AddParameter("ID", "123");
 Form1->REST3Request->Client = Form1->REST3Client;
 Form1->REST3Request->Response = Form1->REST3Response;
 Form1->REST3Response->ContentType = "application/json";
 Form1->REST3Request->Execute();

 // Form1->PHONENumber = 123; Freezing

 // Assignation des variables
 TJSONValue *JV0TOPLevel; JV0TOPLevel = Form1->REST3Response->JSONValue;
 TJSONArray *JSONArray = dynamic_cast<TJSONArray*>(JV0TOPLevel);
 TJSONObject *JSONObject = dynamic_cast<TJSONObject*>(JSONArray->Items[0]);
 TJSONValue *JV0ITEMLevel;

 JV0ITEMLevel = JSONObject->GetValue("FIELDName"); Form1->PHONENumber = JV0ITEMLevel->AsType<String>(); // Freezing

 Queue(NULL, [this](){
   Form1->AniIndicator1->Enabled = false;
   Form1->AniIndicator1->Visible = false;
  }
 );

 ReturnValue = 1;
 Terminate();
}
//---------------------------------------------------------------------------

Any idea of what I missed?知道我错过了什么吗?

Scenario 1:场景 1:

  • TThread::WaitFor() is blocking the main UI thread until the thread finishes running. TThread::WaitFor()阻塞主 UI 线程,直到线程完成运行。 WaitFor() does not process new GUI messages while waiting, but it does process pending Synchronize() and Queue() requests (and does dispatch cross-thread messages sent with SendMessage...() to avoid deadlocks). WaitFor()在等待时不处理新的 GUI 消息,但它确实处理挂起的Synchronize()Queue()请求(并分派使用SendMessage...()发送的跨线程消息以避免死锁)。

  • you can't free the thread object from inside of its OnTerminated event handler.您不能从其OnTerminated事件处理程序内部释放线程对象。 The RTL still needs access to the thread object after the handler exits.在处理程序退出后,RTL 仍然需要访问线程对象。 If you want the thread to free itself, set its FreeOnTerminate property to true instead.如果您希望线程自行释放,请将其FreeOnTerminate属性设置为true However, in this scenario, since you are waiting on the thread, you could just free the thread after WaitFor() exits instead.但是,在这种情况下,由于您正在等待线程,您可以在WaitFor()退出后释放线程。

A better way to handle this scenario is to update the TAniIndicator directly in Button1Tap() and get rid of the OnTerminate event handler altogether.处理这种情况的更好方法是直接在Button1Tap()中更新TAniIndicator并完全摆脱OnTerminate事件处理程序。 The TAniIndicator logic doesn't really belong in the thread's Execute() method to begin with. TAniIndicator逻辑并不真正属于线程的Execute()方法。 However, you will have to manually pump the main thread's message queue for new messages while waiting for the thread to finish.但是,在等待线程完成时,您必须手动从主线程的消息队列中获取新消息。

void __fastcall TForm1::Button1Tap(TObject *Sender, const TPointF &Point)
{
 PHONENumber = _D("");

 AlphaThread = new TAlphaThread(false);

 AniIndicator1->Align = TAlignLayout::Contents;
 AniIndicator1->Enabled = true;
 AniIndicator1->Visible = true;

 HANDLE h = (HANDLE) AlphaThread->Handle;
 while (MsgWaitForMultipleObjects(1, &h, FALSE, INFINITE, QS_ALLINPUT) == (WAIT_OBJECT_0 + 1))
 {
  Application->ProcessMessages();
 }

 int result = AlphaThread->WaitFor();
 delete AlphaThread;

 AniIndicator1->Enabled = false;
 AniIndicator1->Visible = false;

 if (result)
 {
  if(PHONENumber == _D(""))
   ShowMessage(_D("Null value!"));
  else
   Label1->Text = PHONENumber;
 }
}
//---------------------------------------------------------------------------
__fastcall TAlphaThread::TAlphaThread(bool CreateSuspended) : TThread(CreateSuspended)
{
 FreeOnTerminate = false;
}
//---------------------------------------------------------------------------
void __fastcall TAlphaThread::Execute()
{
 // Chargement des données
 Form1->REST1Client->BaseURL = _D("my.URL.php");
 Form1->REST1Request->AddParameter(_D("ID"), _D("123"));
 Form1->REST1Request->Client = Form1->REST1Client;
 Form1->REST1Request->Response = Form1->REST1Response;
 Form1->REST1Response->ContentType = _D("application/json");
 Form1->REST1Request->Execute();

 // Assignation des variables
 TJSONValue *JV0TOPLevel = Form1->REST1Response->JSONValue;
 TJSONArray *JSONArray = static_cast<TJSONArray*>(JV0TOPLevel);
 TJSONObject *JSONObject = static_cast<TJSONObject*>(JSONArray->Items[0]);
 TJSONValue *JV0ITEMLevel = JSONObject->GetValue(_D("FIELDName"));
 Form1->PHONENumber = JV0ITEMLevel->AsType<String>();

 ReturnValue = 1;
}

However, in this scenario, there is really no point whatsoever in having the main thread create a worker thread just to block itself waiting on that thread.但是,在这种情况下,让主线程创建一个工作线程只是为了阻止自己在该线程上等待确实没有任何意义。 You may as well just perform the REST logic directly in the main thread instead.您也可以直接在主线程中执行 REST 逻辑。 You can use the REST components asynchronously to avoid blocking the main thread (ie, by using TRESTRequest::ExecuteAsync() instead of TRESTRequest::Execute() ).您可以异步使用 REST 组件以避免阻塞主线程(即,通过使用TRESTRequest::ExecuteAsync()而不是TRESTRequest::Execute() )。


Scenario 2:场景 2:

  • all of the same issues as Scenario 1.所有与场景 1 相同的问题。

Scenario 3:场景 3:

  • there is no call to TThread::WaitFor() in the main thread, so the Queue() requests go unprocessed, but at least they won't block the worker thread from running.主线程中没有对TThread::WaitFor()的调用,因此Queue()请求未被处理,但至少它们不会阻止工作线程运行。 That would not be the case had you used Synchronize() instead.如果您改为使用Synchronize() ,情况就不会如此。

  • the while loop in the main thread is creating a new lambda on each iteration, but is not actually calling the lambda.主线程中的while循环在每次迭代时创建一个新的 lambda,但实际上并没有调用lambda。 This is just a busy loop that eats up CPU cycles for no gain.这只是一个繁忙的循环,会毫无收获地消耗 CPU 周期。

  • same issue with freeing the thread as the other scenarios.释放线程的问题与其他场景相同。


Now, with all of that said, the correct way to handle this situation using a worker thread is to not wait on the thread at all.现在,综上所述,使用工作线程处理这种情况的正确方法是根本不等待线程。 Let it run normally, and let it notify the main thread when finished.让它正常运行,让它跑完通知主线程。 DO NOT block the main thread at all in the meantime.同时不要阻塞主线程。

Try this:试试这个:

class TGetPhoneNumberThread : public TThread
{
 protected:
    void __fastcall Execute();
 public:
    __fastcall TGetPhoneNumberThread(bool CreateSuspended);
    String PHONENumber;
};
void __fastcall TForm1::Button1Tap(TObject *Sender, const TPointF &Point)
{
 TGetPhoneNumberThread *Thread = new TGetPhoneNumberThread(true);
 Thread->OnTerminate = &GetPhoneNumberThreadTerminated;
 Thread->Start();

 Button1->Enabled = false;
 AniIndicator1->Align = TAlignLayout::Contents;
 AniIndicator1->Enabled = true;
 AniIndicator1->Visible = true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::GetPhoneNumberThreadTerminated(TObject *Sender)
{
 AniIndicator1->Enabled = false;
 AniIndicator1->Visible = false;
 Button1->Enabled = true;

 TGetPhoneNumberThread *Thread = static_cast<TGetPhoneNumberThread*>(Sender);

 if (Thread->FatalException)
   ShowMessage(_D("Error! ") + static_cast<Exception*>(Thread->FatalException)->Message);

 else if (Thread->PHONENumber == _D(""))
   ShowMessage(_D("Null value!"));

 else
   Label1->Text = Thread->PHONENumber;
 }
}
//---------------------------------------------------------------------------
__fastcall TGetPhoneNumberThread::TGetPhoneNumberThread(bool CreateSuspended) : TThread(CreateSuspended)
{
 FreeOnTerminate = true;
}
//---------------------------------------------------------------------------
#include <memory>
void __fastcall TGetPhoneNumberThread::Execute()
{
 std::unique_ptr<TRESTClient> RESTClient(new TRESTClient(NULL));
 std::unique_ptr<TRESTRequest> RESTRequest(new TRESTRequest(NULL));
 std::unique_ptr<TRESTResponse> RESTResponse(new TRESTResponse(NULL));

 // Chargement des données
 RESTClient->BaseURL = _D("my.URL.php");
 RESTRequest->AddParameter(_D("ID"), _D("123"));
 RESTRequest->Client = RESTClient.get();
 RESTRequest->Response = RESTResponse.get();
 RESTResponse->ContentType = _D("application/json");
 RESTRequest->Execute();

 // Assignation des variables
 TJSONValue *JV0TOPLevel = RESTResponse->JSONValue;
 TJSONArray *JSONArray = static_cast<TJSONArray*>(JV0TOPLevel);
 TJSONObject *JSONObject = static_cast<TJSONObject*>(JSONArray->Items[0]);
 TJSONValue *JV0ITEMLevel = JSONObject->GetValue(_D("FIELDName"));

 PHONENumber = JV0ITEMLevel->AsType<String>();
}

Alternatively, you can get rid of the worker thread completely and use TRESTRequest asynchronously instead:或者,您可以完全摆脱工作线程并改为异步使用TRESTRequest

void __fastcall TForm1::Button1Tap(TObject *Sender, const TPointF &Point)
{
 // Chargement des données
 REST1Client->BaseURL = _D("my.URL.php");
 REST1Request->AddParameter(_D("ID"), _D("123"));
 REST1Request->Client = REST1Client;
 REST1Request->Response = REST1Response;
 REST1Response->ContentType = _D("application/json");

 REST1Request->ExecuteAsync(
  [this](){
    // Assignation des variables
    TJSONValue *JV0TOPLevel = REST1Response->JSONValue;
    TJSONArray *JSONArray = static_cast<TJSONArray*>(JV0TOPLevel);
    TJSONObject *JSONObject = static_cast<TJSONObject*>(JSONArray->Items[0]);
    TJSONValue *JV0ITEMLevel = JSONObject->GetValue(_D("FIELDName"));

    PHONENumber = JV0ITEMLevel->AsType<String>();

    TThread::Queue(nullptr,
     [this](){
      AniIndicator1->Enabled = false;
      AniIndicator1->Visible = false;
      Button1->Enabled = true;

      if (PHONENumber == _D(""))
        ShowMessage(_D("Null value!"));
      else
        Label1->Text = PHONENumber;
     }
    );
   }
  },
  false
 );

 Button1->Enabled = false;
 AniIndicator1->Align = TAlignLayout::Contents;
 AniIndicator1->Enabled = true;
 AniIndicator1->Visible = true;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM