概要
あなたは本質的に、長時間実行されるタスクのためにタスクホストを実行し、それらのタスクをキャンセルできることについて話している. あなたの具体的な質問は、.NET でこれを実装する最良の方法を知りたいようです。あなたのアーキテクチャは優れていますが、既存のフレームワークを使用するのではなく独自のフレームワークを作成することに勇気があり、後でアーキテクチャのスケーリングについて言及していません。
私の好みは、TPL Taskオブジェクトを使用することです。キャンセルをサポートし、進行状況などを簡単にポーリングできます。これは .NET 4 以降でのみ使用できます。
基本的にジョブ ホスティング エンジン全体を設計し、.NET のバージョンを知らずにコードを提供することは困難です。サンプルコードを参照しながら、以下で詳細に手順を説明しました。
Windows Service OnCustomCommand を使用するアプローチは問題ありません。クライアント サービス通信にそのオプションがある場合は、メッセージング サービス (以下を参照) を使用することもできます。これは、多数のクライアントが中央ジョブ サービスと通信しており、ジョブ サービスがクライアントと同じマシン上にないシナリオに適しています。
スレッドでのタスクの実行とキャンセル
正確なコンテキストを確認する前に、MSDN - Asynchronous Programming Patternsを確認することをお勧めします。スレッドでジョブを実行およびキャンセルする .NET の主なパターンは 3 つあります。使用する優先順にリストします。
- TAP:タスクベースの非同期パターン
- .NET 4 以降でのみ使用可能になったTaskに基づく
- .NET 4 以降のスレッドベースのアクティビティを実行および制御するための推奨される方法
- その EAP の実装がはるかに簡単
- EAP:イベントベースの非同期パターン
- .NET 4 以降がない場合の唯一のオプションです。
- 実装は難しいが、理解すれば展開でき、非常に信頼性が高い
- APM:非同期プログラミング モデル
- レガシ コードを維持するか、古い API を使用しない限り、もはや関係ありません。
- .NET 1.1 でも EAP のバージョンを実装できるため、独自のソリューションを実装していると言うので、これについては説明しません。
建築学、建築物、建築様式
これを REST ベースのサービスのように想像してください。
- クライアントがジョブを送信し、ジョブの識別子が返されます
- ジョブ エンジンは、ジョブの準備ができたらそれを取得し、実行を開始します。
- クライアントがその仕事をこれ以上必要としない場合は、その識別子を使用してその仕事を削除します
このようにして、クライアントはジョブ エンジンの動作から完全に分離され、ジョブ エンジンは時間の経過とともに改善されます。
ジョブエンジン
アプローチは次のとおりです。
- サブミットされたタスクに対して、次のことができるようにユニバーサル識別子 (UID) を生成します。
- 実行中のタスクを特定する
- 結果の投票
- 必要に応じてタスクをキャンセルする
- その UID をクライアントに返す
- その識別子を使用してジョブをキューに入れる
- リソースがあるとき
- タスクを作成してジョブを実行する
- UID をキーとして Task をディクショナリに保存します
クライアントが結果を必要とする場合、クライアントは UID を含むリクエストを送信し、ディクショナリから取得した Task と照合して進行状況を返します。タスクが完了すると、完了したデータのリクエストを送信できます。または、あなたの場合は、完了したファイルを読みに行くだけです。
キャンセルしたい場合は、UID を使用してリクエストを送信し、タスクを辞書で見つけてキャンセルするように指示することで、タスクをキャンセルします。
ジョブ内でのキャンセル
コード内では、キャンセル トークンを定期的にチェックして、コードの実行を停止する必要があるかどうかを確認する必要があります ( TAP パターンを使用している場合はHow do I abort/cancel TPL Tasks?を、 EAP を使用している場合は Albahari を参照してください)。その時点で、ジョブの処理を終了します。コードが適切に設計されていれば、必要に応じて IDiposable を破棄したり、メモリから大きな文字列を削除したりする必要があります。
キャンセルの基本的な前提は、キャンセル トークンを確認することです。
- 時間のかかる一連の作業の後 (外部 API の呼び出しなど)
- 制御するループ (
for
、foreach
、do
またはwhile
) 内で、各反復をチェックします。
- 「しばらく」時間がかかる可能性のあるシーケンシャル コードの長いブロック内に、定期的にチェックするポイントを挿入します。
キャンセルにどれだけ迅速に対応する必要があるかを定義する必要があります。Windows サービスの場合は、できればミリ秒以内にする必要があります。Windows でサービスの再起動または停止に問題がないことを確認するためです。
このプロセス全体をスレッドで実行し、スレッドを終了する人もいますが、これは見苦しく、推奨されません。
信頼性
質問する必要があります: サーバーが再起動したり、Windows サービスがクラッシュしたり、その他の例外が発生して不完全なジョブが失われたりした場合はどうなりますか? この場合、ジョブを再開したり、まだ開始していないジョブのキューを再構築したりできるようにするために、信頼できるキュー アーキテクチャが必要になる場合があります。
スケーリングしたくない場合、これは簡単です。Windows サービスがジョブ情報を保存したローカル データベースを使用します。
- ジョブの送信時に、その詳細をデータベースに記録します
- ジョブを開始したら、データベースのジョブ レコードに対してそれを記録します
- クライアントがジョブを収集すると、データベースで遅延ガベージ コレクションのマークが付けられ、一定時間 (1 時間、1 日など) 後に削除されます。
- サービスが再起動し、「進行中のジョブ」がある場合は、それらを再度キューに入れ、ジョブ エンジンを再度開始します。
スケールしたい場合、またはクライアントが多数のコンピューター上にあり、1 つ以上のサーバーのジョブ エンジン「ファーム」がある場合は、 を使用して直接通信する代わりに、メッセージ キューを使用することを検討してくださいOnCustomCommand
。
メッセージ キューには複数の利点があります。これにより、ジョブを中央のキューに確実に送信して多くのワーカーが取得して処理できるようになり、クライアントとサーバーを切り離して、ジョブ実行サービスをスケールアウトできるようになります。これらは、ジョブが確実に送信され、高度に分離された方法で処理されることを保証するために使用されます。これは、ローカルまたはグローバルに機能しますが、常に確実に動作し、動的にスケーリングできるクラウド ワーカーで Windows サービスを実行することと組み合わせることができます。
テクノロジの例としては、MSMQ (独自のファイアウォールを維持したい場合、または独自のファイアウォール内に留まらなければならない場合)、またはWindows Azure サービス バス (WASB)があります。どちらの場合でも、エンタープライズ統合のパターンとベスト プラクティスを使用する必要があります。WASB の場合、多くの (MSDN)、多くの (BrokeredMessaging などの MSDN サンプル)、多くの (新しいタスクベースの API)開発者リソース、および使用するNuGet パッケージがあります。