8

「ハートビート」プロセスを実装して、1 日を通して多くのクリーンアップ タスクを繰り返し実行することを検討しています。

これはコマンド パターンを使用する良い機会のように思えたので、次のようなインターフェイスを用意しました。

   public interface ICommand
   {
       void Execute();
       bool IsReady();
   }

次に、実行したいいくつかのタスクを作成しました。以下に基本的な例を示します。

public class ProcessFilesCommand : ICommand
{
    private int secondsDelay;
    private DateTime? lastRunTime;

    public ProcessFilesCommand(int secondsDelay)
    {
        this.secondsDelay = secondsDelay;
    }

    public void Execute()
    {
        Console.WriteLine("Processing Pending Files...");
        Thread.Sleep(5000); // Simulate long running task
        lastRunTime = DateTime.Now;
    }

    public bool IsReady()
    {
        if (lastRunTime == null) return true;

        TimeSpan timeSinceLastRun = DateTime.Now.Subtract(lastRunTime.Value);
        return (timeSinceLastRun.TotalSeconds > secondsDelay);
    }

}

最後に、このループでコンソール アプリケーションが実行され、ThreadPool に追加する待機中のタスクを探します。

class Program
{
    static void Main(string[] args)
    {

        bool running = true;

        Queue<ICommand> taskList = new Queue<ICommand>();
        taskList.Enqueue(new ProcessFilesCommand(60)); // 1 minute interval
        taskList.Enqueue(new DeleteOrphanedFilesCommand(300)); // 5 minute interval

        while (running)
        {
            ICommand currentTask = taskList.Dequeue();
            if (currentTask.IsReady())
            {
                ThreadPool.QueueUserWorkItem(t => currentTask.Execute());
            }
            taskList.Enqueue(currentTask);
            Thread.Sleep(100);
        }

    }
}

オペレーティング システムのクラスで行ったいくつかの作業以外に、マルチスレッドの経験はあまりありません。ただし、どのスレッドも共有状態にアクセスしていないことがわかる限り、それらは問題ないはずです。

これは、私がやりたいことに対して「OK」なデザインのように見えますか? 何か変えることはありますか?

4

5 に答える 5

10

これは素晴らしいスタートです。私たちは最近、このようなことをたくさんやったので、いくつかの提案をすることができます.

  1. 実行時間の長いタスクにはスレッド プールを使用しないでください。スレッド プールは、小さな小さなタスクを多数実行するように設計されています。長時間実行するタスクを実行している場合は、別のスレッドを使用してください。スレッド プールを枯渇させる (すべてのタスクを使い果たす) と、キューに入れられたすべてのものがスレッドプール スレッドが使用可能になるのを待つだけになり、スレッドプールの効果的なパフォーマンスに大きな影響を与えます。

  2. Main() ルーチンで、いつ実行されたか、およびそれぞれが次に実行されるまでの時間を追跡します。各コマンドで同じ「はい、準備ができています」または「いいえ、できません」と言う代わりに、LastRun フィールドと Interval フィールドを用意して、Main() を使用して、各コマンドをいつ実行する必要があるかを判断できます。 .

  3. キューを使用しないでください。Queue タイプの操作のように見えるかもしれませんが、各コマンドには独自の間隔があるため、実際には通常の Queue ではありません。代わりに、すべてのコマンドをリストに入れてから、次の実行までの最短時間でリストを並べ替えます。最初のコマンドの実行が必要になるまでスレッドをスリープさせます。そのコマンドを実行します。次に実行するコマンドでリストを並べ替えます。寝る。繰り返す。

  4. 複数のスレッドを使用しないでください。各コマンドの間隔が 1 分または数分である場合、おそらくスレッドをまったく使用する必要はありません。すべてを同じスレッドで実行することで簡素化できます。

  5. エラー処理。この種のものは、1 つのコマンドの問題がループ全体を失敗させないことを確認するために広範なエラー処理を必要とし、問題が発生したときにデバッグできるようにします。また、エラーが発生した場合にコマンドをすぐに再試行するか、次のスケジュールされた実行まで待機するか、通常よりも遅延させるかを決定することもできます。エラーが毎回発生する場合は、コマンドでエラーをログに記録しないこともできます (頻繁に実行されるコマンドでエラーが発生すると、巨大なログ ファイルが簡単に作成される可能性があります)。

于 2010-01-27T02:39:48.297 に答える
6

すべてをゼロから作成する代わりに、すべてのスケジューリングとスレッド化を処理するフレームワークを使用してアプリケーションを構築することを選択できます。オープンソース ライブラリNCronはまさにこの目的のために設計されており、非常に使いやすいです。

ジョブを次のように定義します。

class MyFirstJob : CronJob
{
    public override void Execute()
    {
        // Put your logic here.
    }
}

そして、次のようなスケジュール設定を含む、アプリケーションのメイン エントリ ポイントを作成します。

class Program
{
    static void Main(string[] args)
    {
        Bootstrap.Init(args, ServiceSetup);
    }

    static void ServiceSetup(SchedulingService service)
    {
        service.Hourly().Run<MyFirstJob>();
        service.Daily().Run<MySecondJob>();
    }
}

このパスをたどることを選択した場合に記述する必要があるコードはこれだけです。必要に応じて、より複雑なスケジュール依存関係の挿入を行うオプションもあり、すぐに使用できるログが含まれています。

免責事項: 私は NCron のリード プログラマーなので、少し偏見があるかもしれません。;-)

于 2010-01-29T23:21:57.323 に答える
2

状態の変更について心配する必要がないように、すべての Command クラスを不変にします。

于 2010-01-27T02:41:01.930 に答える
2

現在、マイクロソフトの「Parallel Extensions」は、並行コードを記述したり、スレッド関連のタスクを実行したりするための実行可能なオプションである必要があります。タスクを完了するために命令的な方法で考える必要がないように、スレッド プールとシステム スレッドの上に優れた抽象化を提供します。

私の意見では、それを使用することを検討してください。ところで、あなたのコードはきれいです。

ありがとう。

于 2010-02-17T07:11:13.483 に答える
0

runningvolatile変数は、その状態が別のスレッドによって変更されるかのようにマークする必要があります。

適合性に関しては、タイマーを使用しないのはなぜですか?

于 2010-01-27T02:32:04.470 に答える