更新:間違ったコードを修正する喜びを否定できませんでした。 「エラーの処理」セクションを追加しました。このセクションでは、受け取ったコメントに基づいて修正されたコードと修正の説明を提供します。
これは、単なる有用な情報ではなく、専門家への質問後の質問です。 ディスカッションに招待します。
最近、幸運にもSvyaznoyの履歴書を.NET開発者の地位に送ることができました。 応答として、マルチスレッドの知識テストを行うように頼まれました。 自分をこの分野の専門家と呼ぶことはできませんが、それでも、次の要件をどのように実装するかが完全に理解できました。
JavaのFixedThreadPoolに似たC#クラスの実装が必要です。次の要件があります。
- このクラスのコンストラクターには、タスクを実行するスレッドの数を渡す必要があります。
- クラスインターフェイスはメソッドを提供する必要があります。ブール実行(タスクタスク、優先度優先)およびvoid stop()
- Taskインターフェースには、任意のスレッドで呼び出されるvoid execute()という1つのメソッドが含まれている必要があります。
- 優先度タイプは、3つの優先度の列挙です:HIGH、NORMAL、LOW。 さらに、キューから次のタスクを選択する際には、次のルールが適用されます:優先度が高の3つのタスク、優先度が通常のタスクが1つ実行され、優先度が低のタスクは実行されませんが、キューには優先度が異なるタスクが少なくとも1つあります。
- stop()メソッドが呼び出される前に、タスクは実行のためにキューに入れられ、ブール実行(タスクタスク、優先度優先)メソッドはタスクの完了を待たずにすぐにtrueを返します。 また、stop()が呼び出された後、新しいタスクは実行キューに追加されず、ブールのexecute(タスクタスク、優先度優先度)メソッドはすぐにfalseを返します。
- stop()メソッドは、現在のすべてのタスクの完了を待ちます(キューをクリアすることなく)。
タスクは、使用するプリミティブ、最も単純なスレッドですべてを実行するか、ThreadPool、TPLなどを使用するかを指定しなかったため、最も基本的な要素であるThread、ManualResetEventsなどを使用することを決定しました。 数時間で書いて送った。 今日、私は人事担当者に電話をかけ、回答を受け取りました。これは次のようなものでした:「必要なものにさえ近づいていません」。 これは私を困惑させました。コードが機能し、テストされているため、明らかな欠陥はありません。
したがって、あなたの判断で、FixedThreadPoolと関連クラスの実装を紹介します。 私はすぐに、彼らの意見では、実装に誤りがあると警告します。したがって、私の考えを基礎として取る価値はありません。 コードに関するコメント:
- タスククラス自体にタスクフローをカプセル化することにしました。
- ILogタイプの2つのパラメーターはテスト目的でのみ必要です。もちろん、これらは主要機能とは関係ありません。
- テストアプリケーションを含むプロジェクト全体は、リンクからダウンロードできます(27キロバイト): ifolderのテストプロジェクト
エラー処理
あなたのコメントと建設的な批判に感謝します。 修正されたソリューションを公開しない場合、トピックを閉じないことにしました。 最初に、推奨事項をリストし、コードに含まれる推奨事項と含まれない推奨事項、およびその理由を説明します。
- ストリームの作成は非常に高価な操作です。 スレッドプールは、すでに作成された複数のスレッドを保存するために必要です。 これが私の主な間違いであり、明らかに、これが私の決定を確認していた技術専門家からそのような否定的な反応を引き起こしたのです。 私はあまりにも問題の条件に集中し、クラス名自体に続く要件を見落としていました。 ありがとう、 Unrul 、 iaroshenko 。 次に、プールコンストラクターで必要な数のスレッドを作成します。 Taskクラスはスレッドを作成しなくなりました。
- スケジューラスレッドは必要ありません。 実際、スレッドの作成がタスククラスからプールクラスに転送された後、タスクをキューからスレッド自体に抽出する機能をシフトできるようになりました。 ありがとう、 iaroshenko 、 romik 。
- タスクインターフェースにイベントがあってはなりません。 タスククラスはスレッドを作成しなくなったため、タスク完了イベントは本当に不要になりました。 ありがとう、 Nagg 。
- IsStoppedのロックは、通常のvolatileフィールドに置き換えることができます。 私はこれに同意しません。 volatile修飾子のおかげで、そのようなフィールドの値を読み取るコードは最新の値を受け取るというMSDNの声明にもかかわらず、そうではありません。 実際に、簡単に言うと、volatileは、コンパイラ、CLR、およびプロセッサが揮発性フィールドとその他のフィールドの読み取り/書き込みを交換しないことを保証します。 詳細はこちらをご覧ください 。 ただし、私の場合、IsStoppedロックは別のロックに必要です。 これがないと、プールが実際に停止した後に新しいタスクがキューに落ちる可能性があり、完了信号が与えられているにもかかわらずタスクフローがスタンバイ状態のままになる場合もあります。 いずれにせよ、マルチスレッドコードの少しの再保険は決して傷つきません。 これは、不規則なエラーを後でキャッチするよりも優れています。 私は口の中で泡でこの解決策を守りません。 誰かがコードに基づいて私の議論に反論できるなら、私は感謝するでしょう。 ありがとう、 elw00d 。
- 優先度キューは、ThreadPool自体のロジックとは別に実装する必要があります。 私もそうしませんでした 実際、プールには多くのコードが含まれておらず、コメントとロギングが多くあります。 しかし、それにもかかわらず、主な理由はこれではありません。 選択条件が変更されたと想像してください。 または、プールインスタンスの作成時に次のタスクを選択する原則をカスタマイズする機能を提供する必要があります。 ソートされたコレクション(キュー)の新しいクラスを作成し、それに応じて、コレクションクラスの数でプールクラスを生成する必要があります。 これは間違った決定です。 この場合、Strategyテンプレートを実装して、次のタスクを選択するためのアルゴリズムを別のクラスに移動することをお勧めします。 この場合の「セレクタ」オブジェクトは、コンストラクタに渡されます。 しかし、これは問題の状態によって必要とされないため、決定を複雑にすることはありません。 ありがとう、 elw00d 。
- インターロックされたメソッドも理由が不明です(モニターとイベントが既に使用されている場合)。 私もこの部分には触れませんでした。 インターロックされた方法により、優先度が高および通常のタスクの選択条件の違反を回避できます。 mQueuedHighPriorityTaskCounterカウンターへのアクセスにはロックがないため、万が一に備えて、Interlockedメソッドを使用して安全に再生することにしました。 おそらくコードのより深い分析は私の間違いを明らかにするでしょうが、私が言ったように、私はマルチスレッドの最大の専門家ではなく、特に安価な場合はストローを置くことを好みます。 ありがとう、 elw00d 。
- 著者がコメントで表明された欠点を解決し、再びソリューションの更新バージョンをSvyaznoyに送信した場合、非常に興味深いでしょう。 もちろん、これはコードと間接的な関係しかありませんが、それでもなお関係があります。 修正されたソリューションをメッセンジャーに送信しないと思います。 私はあなたの助けを利用したので、公平ではありません。 ほとんどの場合、この記事へのリンクを送信し、「既製のスペシャリスト」が必要か、間違いを認めて開発し、先へ進む方法を知っている人が必要かどうかを判断させます。 サポートありがとう、@ ARC_Programmer。
修正されたテストプロジェクトはここからダウンロードできます。 修正されたコード: