4

「マネージャー」のようなクラスのクラスがあります。その機能の1つは、クラスの長時間実行プロセスをシャットダウンする必要があることを通知することです。これは、クラスに「IsStopping」というブール値を設定することによって行われます。

public class Foo
{
    bool isStoping

    void DoWork() {
        while (!isStopping)
        {
            // do work...
        }
    }
}

さて、DoWork()は巨大な関数でした、そして私はそれをリファクタリングすることに決めました、そしてプロセスの一部としてそれのいくつかを他のクラスに分割しました。問題は、これらのクラスの一部には、isStoppingがtrueであるかどうかをチェックする必要がある長時間実行される関数もあることです。

public class Foo
{
    bool isStoping

    void DoWork() {
        while (!isStopping)
        {
            MoreWork mw = new MoreWork()
            mw.DoMoreWork() // possibly long running
            // do work...
        }
    }
}

ここでの私のオプションは何ですか?

isStoppingを参照で渡すことを検討しましたが、外部オブジェクトが必要なため、あまり好きではありません。私は、追加のクラスを可能な限りスタンドアロンで依存性のないものにすることを望んでいます。

isStoppingプロパティを作成してから、内部クラスをサブスクライブできるイベントを呼び出すことも検討しましたが、これは非常に複雑に思えます。

もう1つのオプションは、.net 4タスクが使用するものと同様の「プロセスキャンセルトークン」クラスを作成し、そのトークンをそれらのクラスに渡すことでした。

この状況にどのように対処しましたか?

編集:

また、MoreWorkには、実行時間の長い可能性のあるメソッドをインスタンス化して呼び出すEvenMoreWorkオブジェクトがある可能性があることも考慮してください。私が探しているのは、呼び出しツリーの下にある任意の数のオブジェクトに信号を送って、実行中の処理を停止し、クリーンアップして戻るように指示できる方法だと思います。

EDIT2:

これまでの回答ありがとうございます。使用する方法については実際のコンセンサスがないようで、誰もが異なる意見を持っています。これはデザインパターンのようです...

4

6 に答える 6

5

ここでは2つの方法があります。

1)すでに概説した解決策:シグナリングメカニズムを下位オブジェクトに渡します:bool(refによる)、インターフェイスにクロークされた親オブジェクト自体(Foo: IController以下の例)、またはその他。子オブジェクトは、必要に応じてシグナルをチェックします。

// Either in the MoreWork constructor
public MoreWork(IController controller) {
    this.controller = controller;
}

// Or in DoMoreWork, depending on your preferences
public void DoMoreWork(IController controller) {
    do {
        // More work here
    } while (!controller.IsStopping);
}

2)それを裏返し、オブザーバーパターンを使用します。これにより、従属オブジェクトを親から切り離すことができます。(イベントを使用する代わりに)手動で実行している場合は、下位クラスを変更してIStoppableインターフェイスを実装し、マネージャークラスに停止するタイミングを通知させます。

public interface IStoppable {
    void Stop();
}

public class MoreWork: IStoppable {
    bool isStopping = false;
    public void Stop() { isStopping = true; }
    public void DoMoreWork() {
        do {
            // More work here
        } while (!isStopping);
    }
}

Foo停止可能なもののリストを維持し、独自の停止メソッドで、それらすべてを停止します。

public void Stop() {
    this.isStopping = true;
    foreach(IStoppable stoppable in stoppables) {
        stoppable.Stop();
    }
}
于 2010-06-10T13:08:57.040 に答える
0

マネージャー クラスと他の各ワーカー クラスで Cancel() メソッドを作成できます。インターフェイスに基づいてください。

マネージャー クラス、または他のワーカー クラスをインスタンス化するクラスは、Cancel() 呼び出しをそれらを構成するオブジェクトに伝達する必要があります。

最も深くネストされたクラスは、内部の _isStopping bool を false に設定するだけで、長時間実行されるタスクがそれをチェックします。

あるいは、すべてのクラスが知っている何らかのコンテキストと、キャンセルされたフラグをチェックできる場所を作成することもできます。

別のオプションは、.net 4タスクが使用するものと同様の「プロセスキャンセルトークン」クラスを作成し、そのトークンをそれらのクラスに渡すことでした。

私はこれに慣れていませんが、基本的に bool プロパティフラグを持つオブジェクトであり、各クラスに渡す場合、これが最もクリーンな方法のように思えます。次に、これを取り込んでプライベートメンバー変数に設定するコンストラクターを持つ抽象基本クラスを作成できます。その後、プロセス ループはキャンセルをチェックするだけです。明らかに、ワーカーに渡したこのオブジェクトへの参照を保持して、UI から bool フラグを設定できるようにする必要があります。

于 2010-06-10T12:56:30.430 に答える
0

DoWork()Command パターンを使用して各呼び出しをコマンドに変換することで、呼び出しスタックをフラット化できます。最上位では、実行するコマンドのキュー (または、コマンドがどのように相互作用するかによってはスタック) を維持します。関数の「呼び出し」は、新しいコマンドをキューに入れることに変換されます。次に、各コマンドの処理の間に、キャンセルするかどうかを確認できます。お気に入り:

void DoWork() {
    var commands = new Queue<ICommand>();

    commands.Enqueue(new MoreWorkCommand());
    while (!isStopping && !commands.IsEmpty)
    {
        commands.Deque().Perform(commands);
    }
}

public class MoreWorkCommand : ICommand {
    public void Perform(Queue<ICommand> commands) {
        commands.Enqueue(new DoMoreWorkCommand());
    }
}

基本的に、低レベルの呼び出しスタックを制御するデータ構造に変えることで、各「呼び出し」、一時停止、再開、キャンセルなどの間のものをチェックすることができます.

于 2010-06-11T02:43:43.490 に答える
0

停止フラグをチェックするのが最も賢明な場所ならどこでも、次のようなステートメントでコードを散らかします。

if(isStopping) { throw new OperationCanceledException(); }

OperationCanceledExceptionトップレベルで右にキャッチ。

(a) 頻繁に発生することはなく、(b) 発生しても 1 回しか発生しないため、これによる実際のパフォーマンスの低下はありません。

このメソッドは、WinForms コンポーネントと組み合わせてもうまく機能しBackgroundWorkerます。ワーカーは、スローされた例外をワーカー スレッドで自動的にキャッチし、それを UI スレッドにマーシャリングします。e.Errorプロパティのタイプを確認するだけです。たとえば、次のようになります。

private void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
    if(e.Error == null) {
        // Finished
    } else if(e.Error is OperationCanceledException) {
        // Cancelled
    } else {
        // Genuine error - maybe display some UI?
    }
}
于 2010-06-10T13:38:44.937 に答える
0

サブクラスがサブスクライブするイベントを発生させることは理にかなっていると思います。

于 2010-06-10T06:54:44.287 に答える
0

入れ子になった型は、デリゲートを受け入れる (またはイベントを公開する) ことで、キャンセル条件をチェックできます。次に、マネージャーは、独自の「shouldStop」ブール値をチェックするネストされた型にデリゲートを提供します。このように、唯一の依存関係は、とにかく既に持っていた NestedType の ManagerType です。

class NestedType
{
    // note: the argument of Predicate<T> is not used, 
    //    you could create a new delegate type that accepts no arguments 
    //    and returns T
    public Predicate<bool> ShouldStop = delegate() { return false; };
    public void DoWork()
    {
        while (!this.ShouldStop(false))
        {
            // do work here
        }
    }
}

class ManagerType
{
    private bool shouldStop = false;
    private bool checkShouldStop(bool ignored)
    {
        return shouldStop;
    }
    public void ManageStuff()
    {
        NestedType nestedType = new NestedType();
        nestedType.ShouldStop = checkShouldStop;
        nestedType.DoWork();
    }
}

必要に応じて、この動作をインターフェイスに抽象化できます。

interface IStoppable
{
    Predicate<bool> ShouldStop;
}

また、ブール値をチェックするだけでなく、「停止」メカニズムで例外をスローすることもできます。マネージャーの checkShouldStop メソッドでは、単純に以下をスローできますOperationCanceledException

class NestedType
{
    public MethodInvoker Stop = delegate() { };
    public void DoWork()
    {
        while (true)
        {
            Stop();
            // do work here
        }
    }
}

class ManagerType
{
    private bool shouldStop = false;
    private void checkShouldStop()
    {
        if (this.shouldStop) { throw new OperationCanceledException(); }
    }
    public void ManageStuff()
    {
        NestedType nestedType = new NestedType();
        nestedType.Stop = checkShouldStop;
        nestedType.DoWork();
    }
}

私は以前にこのテクニックを使用したことがありますが、非常に効果的です。

于 2010-06-10T14:03:03.940 に答える