web-dev-qa-db-ja.com

コールバックのあるこのコマンド構造でスタックオーバーフローが発生しないようにするにはどうすればよいですか?

実行する必要がある一連のジョブがあります。これらのジョブを管理するエンティティは、ジョブの構成と開始を担当します。ジョブが完了すると、ジョブはコールバック関数を使用して、ジョブの結果をエンティティに通知します(例外をスローしたかどうかなど)。

public class TaskSet {
    Queue<Job> JobsToExecute;
    void executeJob(Job j);
    void jobFinishedCallback(Job j);
}

public class Job {
    Action<Job> jobFinishedCallback();
    public void Execute() { jobFinishedCallback(this); }
}

ジョブが失敗すると、jobFinishedCallbackはTaskSetのjobFinishedCallbackを呼び出します。ジョブが失敗した場合、TaskSetのjobFinishedCallbackがexecuteJobを再度呼び出します。ジョブが100回失敗すると、それに応じて呼び出しスタックが増加します。 Execute -> jobFInishedCallback -> executeJob ->等々。

最大再試行回数を追加しましたが、ジョブが失敗し続けると、それでも大きなスタックを使用することに不快に感じます。これはコードのにおいだと思います。

この問題を回避するための良い提案はありますか?

2
Zimano

実行する必要がある一連のジョブがあります。

優れた。このニーズを満たすために妥当なアーキテクチャを選択しました。

ジョブが終了すると、ジョブはコールバック関数を使用して、ジョブの結果をエンティティに通知します(例外をスローしたかどうかなど)。

おめでとうございます。あなたはTask<T>に改名し、Jobに変更しました。確立されたライブラリと同じことをすることは、あなたが正しい軌道に乗っていることの証拠ですが、あなたがすでに行われている作業を繰り返しているかもしれないことの証拠でもあります。

たぶんTask<T>、それがあなたのニーズを満たしている場合。タスクには「続行」機能があり、Jobと同じように、タスクに直接コールバックを与えることができることに注意してください。

さらに:C#では、awaitを使用して、タスクベースの非同期ワークフローを非常に簡単に作成できます。コールバックベースの非同期性により、プログラムロジックが裏返しになり、例外処理が困難になります。 コンパイラに負荷のかかる作業を行わせる。ワークフローを継続渡しスタイルに書き換え、例外処理を正しく行います。

ジョブが失敗した場合、TaskSetのjobFinishedCallbackがexecuteJobを再度呼び出します。

あなたが発見したように、それを行うのは間違っています。

この問題を回避するための良い提案はありますか?

再度executeJobを呼び出さないでください。 ジョブを作業キューに再度エンキューし、戻る。そうすることで、無限回帰が発生するだけでなく、再試行fairlyも扱います。再試行はその順番を待つ必要があります。キュー内のすべてのジョブが優先されます。

同様に、ジョブが正常に終了した場合、その継続を直接呼び出さないでください。同じ問題が発生する可能性があります。 継続を呼び出す新しいジョブをエンキューし、thatジョブにnull継続を与えます。そして、これはより公平です。キュー内の他のすべてのジョブが順番を待っているように、継続は順番を待つ必要があります。

より一般的には、今日は、タスク/将来/約束、リアクティブプログラミング、観測可能なシーケンス、トランポリン、継続渡しスタイル、アクターモデルなどについて調査するのに良い日です。あなたは専門家が何十年もの間研究してきたゼロからテクノロジーを再発明しているので、あなた自身の試行錯誤ではなく、蓄積された知識から学ぶ。そしてライブラリでテストされ、デバッグされ、適切に設計されたクラスを使用するではなく、独自のクラスを使用してください。サポートしていない場合は、サポートしていない理由をマイクロソフトにフィードバックしてください。

C#タスクは、「タスクコンテキスト」の概念を使用して、継続がどのようにスケジュールされるかを決定します。再発明は基本的にGUIアプリケーションのコンテキストルールです。 Windowsメッセージキューがワークキューとして使用されます。可能な他の選択肢があります。たとえば、一部のコンテキストは代わりにワーカースレッドをプールから取得し、スレッドで継続を実行します。すでに作成されている既成のパーツを使用することで、システムにすでに設計されている柔軟性とパワーを利用できます。

5
Eric Lippert

コールバックはありません。または少なくとも仕事をそれに渡さないでください。

TaskSet.executeJob(Job j)
{
    j.Execute();    
    //the job has finished
    if(j.status == failed)
    {
        this.Queue.Add(j); //queue for retry
    }
}
1
Ewan