web-dev-qa-db-ja.com

内部状態を持つ部分的に並列なプロデューサー/コンシューマーパターン

電気値の読み取り、処理、保存のために、生産者-消費者パターンを実装する必要があります。これをC#.NET 4.6.1に実装する必要があります。

私はこれを非常に詳細に説明するようにしていますので、私が達成したい目標について誤解がないようにしています。私はそれを行う方法を考えていますが、それが最善の方法ではないかもしれません。

私のアイデアについてのあなたの考えとそれを解決するための提案を知りたいのですが。

状況

値は、いくつかのデータロガーから別のプログラムによって収集されます。これらの値は、各データロガーのファイルに保存されます。私が書いているソフトウェアには、入力ファイルのディレクトリ、結果を出力する場所、入力に使用される形式、出力に使用される形式などを指定する構成ファイルがあります。各データロガーで重要なことは各(電気)値の処理方法に関する定義が存在します。

ファイルの数、サイズ、データロガーの数は大きく異なります。私はおそらく1から200程度のデータロガーを想定します。

目標

同時I/Oで各データロガーのファイルを並列処理したい。つまり、プログラムが新しいファイルを読み取る間、以前に読み取られたファイルの内容が処理され、以前の計算の結果がファイルに書き込まれます。処理は、データロガー間で並行して行われる必要があります。

値の処理を内部で並列化できないことが重要です。 1つのデータロガーの値は、順次(時系列で)処理されます。スループットを最適化してメモリ使用量を制限するには、I/Oと処理の間、場合によっては処理ステップの間にもバッファリングが必要です。

小さなスケッチ

+----------+               +----------------+               +-----------+ 
|Read files|---->Queue---->|Process values  |---->Queue---->|Write files|
+----------+         |     |of data logger 1|               +-----------+ 
   (Task)            |     +----------------+                  (Task) 
                     |     +----------------+
                     |---->|Process values  |
                     |     |of data logger 2|
                     |     +----------------+
                     ...       (n Tasks) 

アイデア

私の考えは、タスクパラレルライブラリ(TPL)データフローを使用することです。これは次のようになります。

+--------------+     +--------------+     +----------------+               +-----------+ 
|TransformBlock|     |  BufferBlock |     | TransformBlock |               |ActionBlock|  
|  Read files  |---->|     Queue    |---->| Process values |---->Queue---->|Write files|
+--------------+     +--------------+     |of data logger 1|               +-----------+ 
   (1 Block)                        |     +----------------+                 (1 Block) 
                                    |     +----------------+
                 conditional linking|     | TransformBlock |
                                    |---->| Process values |
                                    |     |of data logger 2|
                                    |     +----------------+
                                    ...       (n Blocks) 

読み込まれるデータがすべてのTransformBlockを対象としているわけではないため、 条件付きリンク を使用します。値を処理する各TransformBlockは、処理関数を含むクラスのインスタンスとともに、データロガーの数に応じてループで生成されます。

計算には状態があるため、処理するオブジェクトがいくつかあることが重要です。すべての入力値を合計したい場合、状態の簡単な例は最後に計算された値です。もう1つは最後のタイムスタンプである可能性があるため、新しい値が時系列的に正しいことを検証できます。もちろん、状態はオブジェクトごと、つまりブロックとデータロガーごとに存在します。ロックは必要ありません。

maxDegreeOfParallelism内の処理はシーケンシャルでなければならないため、TransformBlocksは常に1に設定されます。

条件付きリンクは、ファイルが属する場所を常に確認する必要があるため、パフォーマンスに影響を与える可能性があります。メッセージを送信するブロックを直接選択する方法がある場合、これにより時間を節約できます。 I/Oの複数のタスクがパフォーマンスを低下させる可能性があるため、ファイルを読み取るために各データロガーにブロックを使用しないことを選択します。

私の考えについてどう思いますか?どうすれば改善できますか?まったく異なるアプローチをとりますか?

私は正直である必要があります Stack Overflowで非常によく似た質問 を既に投稿しましたが、問題を十分に説明していなかったようです。

5
John

私はこれを十分に考え、TasksとBlockingCollectionsを使用するコンシューマー/プロデューサーパターンが最も簡単で最も効率的なソリューションになるはずです。

+----------+                          +----------------+                           +-----------+ 
|   Task   |                          |      Task      |                           |   Task    |
|Read files|-----BlockingCollection-->|Process values  |----BlockingCollection---->|Write files|
+----------+   |                      |of data logger 1|  |                        +-----------+ 
               |                      +----------------+  |             
               |                      +----------------+  |
               |                      |      Task      |  |
               |-BlockingCollection-->|Process values  |--|
               |                      |of data logger 2|  |
               |                      +----------------+  |
                ...                       (n Tasks)        ...

BlockingCollectionsが最初に作成され、ファイルを読み取るタスクのコンストラクターに渡されます。データ処理タスクに必要なBlockingCollectionは、データロガーの処理ユニットを表すオブジェクトにも渡されます。データ処理の各タスクは、対応するオブジェクトの機能を実行します。これは、内部状態も保持します。

ファイルを読み取るタスクは、さまざまなデータロガーファイルを区別し、データを正しいBlockingCollectionに渡すことができます。使用される戦略に応じて、条件付きリンクを使用してTPL Dataflowとは対照的にデータをどこに渡すかを既に認識しています。

2
John