6

私は自分が抱えているデザインの苦境を解決しようとしています。

ClassWithLongOperation
{
    Run()
    {
        RecrusiveOperation();
    }

    RecrusiveOperation()
    {
        /* RECURSION */
    }
}

MyThread
{
    ClassWithLongOperation Op1(10);
    Op1.Run();  // Takes several minutes.

    ClassWithLongOperation Op2(20);
    Op2.Run();

    SomeOtherClassWithLongOperation Op3;
    Op3.Run();

    // Do some other stuff
}

GUI はMyThreadを開始します。これは 5 ~ 6 分間実行されます。ユーザーが操作をキャンセルできるように、GUI に大きなキャンセル ボタンを表示できるようにしたいと考えています。

グローバルブール変数bCancelledを作成し、 RecursiveOperationに設定されているかどうかを確認できますが、優れた C++ & OO プログラマーになり、グローバル変数を避けたいと考えています。特に、複数のファイルに分散する必要がある場合。

では、(適切な設計に従って) MyThreadを安全にキャンセルするにはどうすればよいでしょうか? これを許可するには、セットアップで何を変更できますか?

スレッドを開始するためにも使用_beginthreadexしていますが、より簡単な解決策が可能であれば、boost を使用できます。

4

5 に答える 5

4

フラグはプログラム全体に対してグローバルである必要はありませんが、クラス コードから見えるようにする必要があります。プライベート インスタンス メンバーとなるフラグと、それを false/true に変更するパブリック関数を作成します。再帰関数で、その値をテストして、タスクを続行する必要があるかどうかを確認します。必要に応じて、値を false に設定して (もちろん関数を介して) 再帰呼び出しを停止します。つまり、ユーザーがボタンをクリックすると、目的のインスタンスで関数が呼び出されます。この方法では、プライベート フラグとそれを安全に変更するためのパブリック メンバー関数があるため、オブジェクト指向の原則に違反することはありません。

于 2012-09-06T12:49:37.933 に答える
3

グローバル変数を使用することは、実際には世界で最悪のことではありません。不要なグローバル変数が急増すると、メンテナンスの悪夢につながりますが、実際には、ここでは迅速でわかりやすい解決策のように思えます。しかし、クリーンな OO ソリューションが必要な場合、これは確かに可能です。

編集私の元の投稿では、複数の操作を順番に実行できるようにしたいという事実を見落としており、それらのいずれかがキャンセルされた場合、残りの操作は実行されません。これは、boolキャンセル可能な操作ごとに個別にフラグを保持するのではなく、キャンセラー内にフラグを保持する方が便利であることを意味します。例外は、実際の制御フローを処理する最も優れた方法です。また、いくつかのことを強化しました (volatileフラグ自体の追加、名前の明確化、不要なアクセス権の制限)。

// A thing that can cancel another thing by setting a bool to true.
class Canceller {
public:
    Canceller : cancelledFlag(false) {}

    void RegisterCancellee(Cancellee const& c) {
        c.RegisterCanceller(cancelledFlag);
    }

    void Cancel() {
        cancelledFlag = true;
    }

private:
    volatile bool cancelledFlag;
};

class CancelButton : public Canceller {
    ...
    // Call Cancel() from on-click event handler
    ...
};

class Cancellation : public std::exception {
public:
    virtual const char* what() const throw() {
        return "User cancelled operation";
    }
};

// A thing that can be cancelled by something else.
class Cancellee {
    friend class Canceller;    // Give them access to RegisterCanceller()

protected:
    Cancellee() : pCancelledFlag(0) {}

    // Does nothing if unconnected
    void CheckForCancellation() {
        if (pCancelledFlag && *pCancelledFlag) throw Cancellation();
    }

private:
    void RegisterCanceller(volatile bool& cancelledFlag) {
        pCancelledFlag = &cancelledFlag;
    }

    volatile bool* pCancelledFlag;
};

class Op1 : public Cancellee {   // (And similarly for Op2 and Op3)
    ...
    // Poll CheckForCancellation() inside main working loop
    ...
};

MyThread
{
    CancelButton cancelButton("CANCEL!");

    try {
        ClassWithLongOperation Op1(10);
        cancelButton.RegisterCancellee(Op1);
        Op1.Run();  // Takes several minutes.

        ClassWithLongOperation Op2(20);
        cancelButton.RegisterCancellee(Op2);
        Op2.Run();

        SomeOtherClassWithLongOperation Op3;
        cancelButton.RegisterCancellee(Op3);
        Op3.Run();
    } catch (Cancellation& c) {
        // Maybe write to a log file
    }

    // Do some other stuff
}

「ダブル バウンス」登録により、キャンセラはプライベート フラグ変数にアクセスできます。

最も重要なことは、非常に特殊な場合を除いて、スレッド終了関数を使用しないことです。なんで?彼らはデストラクタを実行しません。また、ターゲットスレッドに「クリーンアップ」する機会も与えません。

于 2012-09-06T13:10:16.323 に答える
1

グローバル変数を使用する代わりに、内部ブール変数を設定する cancelOperation() のようなメソッドを ClassWithLongOperation および/または MyThread に追加します。適切なクラス メソッドは、適切なタイミングで変数をチェックする必要があります。

于 2012-09-06T12:48:33.573 に答える
1

ClassWithLongOperation に Stop() メソッドを実装し、BigFatCancelButton のイベント ハンドラーで現在の操作に対してこの Stop() メソッドを呼び出すことができます。

于 2012-09-06T12:49:09.447 に答える
0

... または、Stop() メソッドを Thread クラスに追加して、作業オブジェクトが実行中のスレッドを認識できるようにします。作業オブジェクトの Stop() メソッドをスローすることもできます。より重要なことに応じて、スレッドまたは作業オブジェクトを停止します。

于 2012-09-06T13:02:07.230 に答える