3

Embarcadero CB10.1 の再入可能性に関する問題の処理方法について、アドバイスをお願いしたいと思います。「すべての最適化を無効にする」を true に設定して、デバッグ構成でコンパイルします。私はWin7で実行しています。

簡単なテストケースがあります。2 つのボタンがあるフォーム。各ボタンの OnClick イベント ハンドラーは、CPU を集中的に使用する同じ関数を呼び出します。以下は、ヘッダー ファイルとそれに続くプログラム ファイルです。

#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published:    // IDE-managed Components
    TButton *Button1;
    TButton *Button2;
    void __fastcall Button1Click(TObject *Sender);
    void __fastcall Button2Click(TObject *Sender);
private:    // User declarations
    double __fastcall CPUIntensive(double ButonNo);
    double __fastcall Spin(double Limit);

public:     // User declarations
    __fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif



//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}

//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
Button1->Caption = "Pushed";
double retv = CPUIntensive(1);
Button1->Caption = "Button1";
if (retv) ShowMessage("Button1 Done");
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button2Click(TObject *Sender)
{
Button2->Caption = "Pushed";
double retv = CPUIntensive(2);
Button2->Caption = "Button2";
if (retv) ShowMessage("Button2 Done");
}
//---------------------------------------------------------------------------

double __fastcall TForm1::CPUIntensive(double ButtonNo)
{
//
static bool InUse = false;
if (InUse) {
    ShowMessage("Reentered by button number " + String(ButtonNo));
    while (InUse) {};
    }
double retv;
InUse = true;
retv = Spin(30000);         // about 9 seconds on my computer
//retv += Spin(30000);      // uncomment if you have a faster computer
//retv += Spin(30000);
InUse = false;
return retv;
}
//---------------------------------------------------------------------------

double __fastcall TForm1::Spin(double Limit)
{
double k;
for (double i = 0 ; i < Limit ; i++) {
    for (double j = 0 ; j < Limit ; j++) {
        k = i + j;
        // here there can be calls to other VCL functions
        Application->ProcessMessages(); // added so UI would be responsive (2nd case)
        }
    }
return k;
}
//---------------------------------------------------------------------------

- 1 番目のケース: 表示されているコードですが、ProcessMessages() への呼び出しはありません。

これを実行してボタン 1 をクリックすると、CPU 使用率が約 9 秒間ほぼ 100% に跳ね上がります。この間、フォームは応答しなくなります。フォームを移動したり、ボタン 2 をクリックしたりできません。

それは私が期待するように機能します。

2 番目のケース : CPU を集中的に使用する機能中にフォームをユーザーに応答させるために、示されているように ProcessMessages() 呼び出しを追加しました。これで、フォームを移動して他のボタンをクリックできるようになりました。

ボタン 1 をもう一度クリックしたり、ボタン 2 をクリックしたりすることもできるため、これは必ずしも良いことではありません。CPU を集中的に使用する関数が 2 回目に実行されるのを防ぐために、静的なブール値フラグ "InUse" を作成しました。関数の開始時に設定し、関数の完了時にクリアします。

そのため、CPU を集中的に使用する関数に入ったときにフラグを確認し、フラグが設定されている場合 (ボタンを前回クリックして設定されている必要があります)、メッセージを表示してフラグがクリアされるのを待ちます。

しかし、フラグはクリアされず、私のプログラムは「while」ステートメントで永遠にループします。CPU を集中的に使用する関数が完了するのを待ってから、もう一度実行するようにプログラムを設定したいと思います。

デッドロックに達した後に Spin() 関数にブレークポイントを設定すると、どちらのイベントも実行されていないことが示され、決して起動されません。

VCL がスレッド セーフではないことはわかっていますが、ここではすべての処理がメイン スレッドで行われます。私の実際のコードでは、VCL 関数の呼び出しが多いため、CPU を集中的に使用する関数はメイン スレッドに残す必要があります。

クリティカル セクションとミューテックスを検討しましたが、すべてがメイン スレッドにあるため、それらを使用してもブロックされません。

多分それはスタックの問題ですか?デッドロックなしでこれを処理できるソリューションはありますか?

4

2 に答える 2

3

2 番目のケース : CPU を集中的に使用する機能中にフォームをユーザーに応答させるために、示されているように ProcessMessages() 呼び出しを追加しました。これで、フォームを移動して他のボタンをクリックできるようになりました。

それは常に間違った解決策です。この状況を処理する正しい方法は、CPU を集中的に使用するコードを別のワーカー スレッドに移動し、そのスレッドがまだ実行されていない場合は、ボタン イベントでそのスレッドの新しいインスタンスを開始することです。または、実行する作業がないときにスリープするループ内でスレッドを実行し続け、各ボタン イベントでスレッドにウェイクアップして作業を行うように通知します。いずれにせよ、メインUIスレッドをブロックしないでください。

ボタン 1 をもう一度クリックしたり、ボタン 2 をクリックしたりすることもできるため、これは必ずしも良いことではありません。

CPU を集中的に使用する関数が 2 回目に実行されるのを防ぐために、静的なブール値フラグ "InUse" を作成しました。関数の開始時に設定し、関数の完了時にクリアします。

より良い方法は、作業の実行中にボタンを無効にし、完了したら再度有効にすることです。その場合、その作品は最初から再入力できません。

ただし、フラグを保持していても、フラグが既に設定されている場合は、関数は何もせずに終了する必要があります。

いずれにせよ、作業が進行中であることをユーザーに知らせる UI を表示する必要があります。作業が別のスレッドで行われる場合、これは管理が容易になります。

そのため、CPU を集中的に使用する関数に入ったときにフラグを確認し、フラグが設定されている場合 (ボタンを前回クリックして設定されている必要があります)、メッセージを表示してフラグがクリアされるのを待ちます。

しかし、フラグはクリアされず、

これは、何もしない無限ループを実行しているだけで、コードがそれ以上進行できないためです。そして確かに、既存の作業を終了してフラグをリセットすることはありません。

書き直さずに既存のコードに加えることができる最小の修正は、 whenの代わりにtrueCPUIntensive()を使用するように変更することです。これにより、 への呼び出しが終了し、実行が完了するのを待っている前の呼び出しに制御を戻すことができます。return 0while (InUse) {}InUseProcessMessages()CPUIntensive()

VCL がスレッド セーフではないことはわかっていますが、ここではすべての処理がメイン スレッドで行われます。

タイは大きな間違いです。

私の実際のコードでは、VCL 関数の呼び出しが多いため、CPU を集中的に使用する関数はメイン スレッドに残す必要があります。

これは、メイン スレッドで作業を実行する十分な理由ではありません。それを所属するワーカー スレッドに移動し、UI にアクセスする必要があるときはいつでもメイン スレッドと同期させます。ワーカー スレッドでできるだけ多くの作業を行い、絶対に必要な場合にのみ同期します。

于 2017-01-09T05:55:23.957 に答える