2

短い質問:

キューに送信された作業項目を処理する単一のバックグラウンド スレッドを生成したいと考えています (1 つのスレッドを持つスレッドプールなど)。作業項目には、進行状況を報告できるものとできないものがあります。.NET の無数のマルチスレッド アプローチのうち、どれを使用する必要がありますか?


長い説明(意味をなさない半分について尋ねるのを避けるため):

私の winforms アプリケーションのメイン ウィンドウは、縦に 2 つに分割されています。左半分には、アイテムを含むツリービューが含まれています。ユーザーがツリービュー内の項目をダブルクリックすると、その項目が右半分に開かれます。ほとんどすべてのオブジェクトには、複数のセクション (タブで表される) に分割された多数のプロパティがあります。これらのプロパティの読み込みにはかなりの時間がかかり、通常は約 10 秒、場合によってはそれ以上かかります。また、時々プロパティが追加されるため、時間が長くなります。

現在、私のシングルスレッド設計では、UI が応答しなくなります。当然、これは望ましくありません。バックグラウンドでパーツごとにロードし、パーツがロードされるとすぐに使用できるようにしたいと思います。他の部分については、ローディング アニメーションなどを含むプレースホルダー タブを表示します。また、1 回の長いモノリシック操作で読み込まれる部分もあれば、多数の小さな関数呼び出しと計算で構成される部分もあるため、読み込みの進行状況を表示できます。これらの部分については、進捗状況を確認できれば幸いです (特に、どこかにぶら下がっている場合)。

データ ソースはスレッド セーフではないため、2 つの部分を同時に読み込むことはできません。

この動作を実装するには、どのようなアプローチが最適でしょうか? 私の肩からいくつかの作業を持ち上げる.NETクラスはありますか、それとも単に降りて汚れるべきThreadですか?

AThreadPoolは作業項目のキュー管理を行いますが、進行状況を報告する機能はありません。BackgroundWorker一方、進行状況レポートをサポートしていますが、これは単一の作業項目を対象としています。おそらく両方の組み合わせはありますか?

4

3 に答える 3

2

.NET 4.0 では、Task非同期の可能性がある単一の操作を表す型を導入することで、マルチスレッドが大幅に改善されています。

あなたのシナリオでは、各プロパティ (またはプロパティ グループ) の読み込みを別々のタスクに分割することをお勧めします。タスクには「親」の概念が含まれているため、各オブジェクトの読み込みは、プロパティ読み込みタスクを所有する親タスクになる可能性があります。

キャンセルを処理するには、新しい統合キャンセル フレームワークを使用します。CancellationTokenSourcefor each オブジェクトを作成し、CancellationTokenそれを親タスクに渡します (親タスクはそれを各子タスクに渡します)。これにより、オブジェクト全体が完了するまで待つのではなく、現在読み込み中のプロパティが完了した後に有効になるオブジェクトをキャンセルできます。

同時実行 (より正確にはOrderedTaskScheduler同時実行) を処理するには、ParallelExtensionsExtras サンプル ライブラリのを使用します。それぞれTaskは、スケジュールする必要がある作業単位のみを表し、 を使用OrderedTaskSchedulerすることで、(ThreadPool スレッドで) 順次実行を保証します。

UI の進行状況の更新は、UI の更新を作成し、Taskそれを UI スレッドにスケジュールすることで実行できます。私のブログにこの例があり、いくつかの扱いにくいメソッドをProgressReporterヘルパー型にラップしています。

この型の良い点の 1 つTaskは、例外とキャンセルを自然な方法で伝播することです。これらは、多くの場合、あなたのような問題を処理するシステムを設計する上でより困難な部分です。

于 2010-08-13T10:25:22.827 に答える
1

スレッドを使用し、スレッドセーフコレクションに作業をドロップし、UIを更新するときにinvokeを使用して、正しいスレッドでそれを実行します

于 2010-08-13T09:43:12.667 に答える
1

トリッキーですね!

あなたのデータ ソースはスレッドセーフではないと言います。では、これはユーザーにとって何を意味するのでしょうか。あらゆる場所をクリックしているが、プロパティが読み込まれるのを待たずに別の場所をクリックする場合、読み込みに時間がかかる 10 個のノードをクリックしてから、10 個目のノードを待っている可能性があります。データ ソースへのアクセスはスレッド セーフではないため、ロードは次々に実行する必要があります。これは、ThreadPool がロードを並行して実行し、スレッドの安全性を損なうため、適切な選択ではないことを示しています。ユーザーが見たいページの読み込みを開始する前に、最後の 9 つのノードが読み込まれるのを待つ必要がないように、読み込みを途中で中止できるとよいでしょう。

読み込みを中止できる場合は、BackgroundWorker が最適です。ユーザーがノードを切り替え、BackgroundWorker が既にビジーである場合は、イベントまたは何かを設定して、既存の作業を中止し、新しい作業をキューに入れて現在のページをロードする必要があることを通知します。

また、スレッド プールで実行されているスレッドに進行状況を報告させるのはそれほど難しいことではありません。これを行うには、進行状況オブジェクトを次のようなタイプの QueueUserWorkItem 呼び出しに渡します。

class Progress
{
  object _lock = new Object();
  int _current;
  bool _abort;

  public int Current
  {
    get { lock(_lock) { return _current; } }
    set { lock(_lock) { _current = value; } }
  }

  public bool Abort
  {
    get { lock(_lock) { return _abort; } }
    set { lock(_lock) { _abort = value; } }
  }
}

スレッドはこれに書き込むことができ、UI スレッドは (System.Windows.Forms.Timer イベントから) ポーリングして進行状況を読み取り、進行状況バーまたはアニメーションを更新できます。

また、Abort プロパティを含める場合。ユーザーがノードを変更すると、UI で設定できます。load メソッドは、操作中のさまざまな時点で中止値をチェックし、設定されている場合は、読み込みを完了せずに戻ることができます。

正直に言うと、どちらを選択してもそれほど重要ではありません。3 つのオプションはすべて、バックグラウンド スレッドで処理を実行します。もし私があなたなら、セットアップが非常に簡単な BackgroundWorker から始めます。さらに何かが必要であると判断した場合は、後で ThreadPool またはプレーン Thread に切り替えることを検討してください。

BackgroundWorker には、完了したイベント (メインの UI スレッドで実行される) を使用して、読み込まれたデータで UI を更新できるという利点もあります。

于 2010-08-13T09:24:34.407 に答える