12

現在、順次実行されるキューに多数のC#計算(メソッド呼び出し)があります。各計算では、待ち時間の長いサービス(ネットワーク、ディスクなど)が使用されます。

モノコルーチンを使用して、前の計算が高遅延サービスが戻るのを待っている間、計算キュー内の次の計算を続行できるようにしました。ただし、私はモノコルーチンに依存しないことを好みます。

高遅延サービスが戻るのを待っている間に追加の計算を処理できるようにする、純粋なC#で実装可能なデザインパターンはありますか?

ありがとう

アップデート:

膨大な数(> 10000)のタスクを実行する必要があり、各タスクは高レイテンシのサービスを使用します。Windowsでは、それほど多くのスレッドを作成することはできません。

アップデート:

基本的に、Stackless Python(http://www.stackless.com/)のタスクレットの利点(次のように)をエミュレートするデザインパターンが必要です。

  1. 膨大な数のタスク
  2. タスクがブロックされた場合、キュー内の次のタスクが実行されます
  3. 無駄なCPUサイクルはありません
  4. タスク間の最小限のオーバーヘッド切り替え
4

10 に答える 10

9

IEnumerable を使用して協調マイクロスレッディングをシミュレートできます。残念ながら、これはブロッキング API では機能しないため、ポーリングできる API、またはシグナリングに使用できるコールバックを持つ API を見つける必要があります。

方法を検討する

IEnumerable Thread ()
{
    //do some stuff
    Foo ();

    //co-operatively yield
    yield null;

    //do some more stuff
    Bar ();

    //sleep 2 seconds
    yield new TimeSpan (2000);
}

C# コンパイラはこれをステート マシンにアンラップしますが、外観は共同マイクロスレッドのようになります。

パターンは非常に簡単です。すべてのアクティブな IEnumerator のリストを保持する「スケジューラ」を実装します。リストを循環しながら、MoveNext () を使用してそれぞれを「実行」します。MoveNext の値が false の場合、スレッドは終了しており、スケジューラはそのスレッドをリストから削除します。true の場合、スケジューラは Current プロパティにアクセスして、スレッドの現在の状態を判断します。それが TimeSpan の場合、スレッドはスリープを希望し、スケジューラはスレッドを何らかのキューに移動しました。このキューは、スリープ タイムスパンが終了したときにメイン リストにフラッシュバックできます。

他のリターン オブジェクトを使用して、他のシグナリング メカニズムを実装できます。たとえば、ある種の WaitHandle を定義します。スレッドがこれらのいずれかを生成すると、ハンドルが通知されるまで待機キューに移動できます。または、待機ハンドルの配列を生成することで、WaitAll をサポートできます。優先順位を実装することもできます。

私は約 150LOC でこのスケジューラーの簡単な実装を行いましたが、まだコードをブログに書いていません。これは PhyreSharp PhyreEngine ラッパー (非公開) 用であり、デモの 1 つで数百の文字を制御するのに非常にうまく機能しているようです。Unity3D エンジンから概念を借用しました。Unity3D エンジンには、ユーザーの観点から説明するオンライン ドキュメントがいくつかあります。

于 2009-08-24T16:46:26.723 に答える
6

.NET 4.0 には、タスクの並列処理に対する広範なサポートが付属しています。

于 2009-08-23T19:39:37.427 に答える
5

スレッド プールを使用して、タスク キューから供給されるアクティブなタスクのリストを使用して、管理可能なバッチで一度に複数のタスクをキューから実行することをお勧めします。

このシナリオでは、メイン ワーカー スレッドは、最初に N 個のタスクをキューからアクティブ タスク リストにポップして、スレッド プールにディスパッチします (ほとんどの場合、 QueueUserWorkItemを使用します)。N は、スレッド プールを過負荷にしない管理可能な量を表します。スレッドのスケジューリングと同期のコストでアプリがダウンしたり、各タスクの I/O メモリのオーバーヘッドが組み合わさって利用可能なメモリを使い果たしたりする可能性があります。

タスクがワーカー スレッドに完了を通知するたびに、そのタスクをアクティブ タスク リストから削除し、実行するタスク キューから次のタスクを追加できます。

これにより、キューから N 個のタスクのローリング セットを取得できます。N を操作してパフォーマンス特性に影響を与え、特定の状況で最適なものを見つけることができます。

最終的にはハードウェア操作 (ディスク I/O とネットワーク I/O、CPU) がボトルネックになるので、小さいほど良いと思います。ディスク I/O で動作する 2 つのスレッド プール タスクは、1 つより速く実行されることはほとんどありません。

また、アクティブなタスク リストを特定のタイプのタスクのセット数に制限することで、アクティブなタスク リストのサイズと内容に柔軟性を実装することもできます。たとえば、4 コアのマシンで実行している場合、最もパフォーマンスの高い構成は、1 つのディスクにバインドされたタスクとネットワーク タスクと共に同時に実行される 4 つの CPU にバインドされたタスクであることがわかります。

すでにディスク IO タスクとして分類されているタスクが 1 つある場合は、そのタスクが完了するまで待ってから別のディスク IO タスクを追加することを選択でき、その間に CPU バウンドまたはネットワークバウンドのタスクをスケジュールすることを選択できます。

これが理にかなっていることを願っています!

PS: タスクの順序に依存関係はありますか?

于 2009-08-24T01:01:59.467 に答える
2

Concurrency and Coordination Runtimeをぜひチェックしてください。彼らのサンプルの 1 つは、あなたが話していることを正確に説明しています。長い待ち時間のサービスを呼び出すと、CCR は、待機中に他のタスクを効率的に実行できるようにします。必要に応じてすべてのコアを使用しますが、タスクごとにスレッドを生成する必要がないため、膨大な数のタスクを処理できます。

于 2009-08-26T17:31:46.907 に答える
1

これはマルチスレッド処理の従来の使い方ではないでしょうか。

Reactor などのパターンはこちら

于 2009-08-23T19:45:14.780 に答える
1

Async IOを使用するように記述するだけで十分な場合があります。

これにより、デザインに強力な構造がないと、厄介でデバッグが困難なコードになる可能性があります。

于 2009-08-23T19:45:46.633 に答える
1

これを見てください:

http://www.replicator.org/node/80

これはまさにあなたが望むことをするはずです。しかし、それはハックです。

于 2010-11-23T17:52:44.620 に答える
0

はい、もちろんできます。指定したラムダをコールバックしてキューに入れるディスパッチャ メカニズムを構築するだけです。Unity で書くすべてのコードはこのアプローチを使用しており、コルーチンは使用していません。WWW などのコルーチンを使用するメソッドをラップして、それを取り除くだけです。理論的には、オーバーヘッドが少ないため、コルーチンは高速になります。実際には、かなり些細なタスクを実行するために新しい構文が言語に導入されます。さらに、コルーチンのエラーでスタック トレースを適切にたどることができません。表示されるのは ->Next だけだからです。次に、キュー内のタスクを別のスレッドで実行する機能を実装する必要があります。ただし、最新の .net には並列関数があり、基本的に同様の機能を作成することになります。実際には多くのコード行ではありません。

誰かが興味を持っているなら、私はコードを送ります。私には持たないでください。

于 2014-02-20T05:37:10.800 に答える
0

.NET での実装に関する「リアクティブ」パターン (別の投稿者が言及) に関する詳細情報。別名「Linq to Events」

http://themechanicalbride.blogspot.com/2009/07/introducing-rx-linq-to-events.html

-オイシン

于 2009-08-24T00:19:47.583 に答える
0

実際、タスクに 1 つのスレッドを使用すると、ゲームに負けます。Node.js が膨大な数の接続をサポートできる理由を考えてみてください。非同期IOでいくつかのスレッドを使用!!! これには、非同期関数と待機関数が役立ちます。

foreach (var task in tasks)
{
    await SendAsync(task.value);
    ReadAsync(); 
}

SendAsync() と ReadAsync() は、IO 呼び出しを非同期にする偽の関数です。

タスクの並列処理も適切な選択です。しかし、どちらが速いかはわかりません。あなたの場合、両方をテストできます。

于 2013-12-28T02:20:38.400 に答える