2010-12-29 14 views
3

私がしようとしていることの簡単な前書き。私はプロセスを開始し、stderrとstdinを監視するために2つのスレッドを起動したい。各スレッドはストリームのビットを噛み砕き、それをNetworkStreamに送り出します。いずれかのスレッドにエラーがある場合は、両方のスレッドがただちに終了する必要があります。StreamReader.PeekとThread.Interruptの代わりに

stdoutおよびstdin監視スレッドを持つこれらの各プロセスは、メインサーバープロセスによって分離されます。これが難しくなる理由は、いつでもこれらのプロセスが40または50になりやすいからです。朝の再起動バーストの間だけ、50以上の接続がありますが、実際には100以上を処理できる必要があります。私は100の同時接続でテストします。

try 
{ 
    StreamReader reader = this.myProcess.StandardOutput; 

    char[] buffer = new char[4096]; 
    byte[] data; 
    int read; 

    while (reader.Peek() > -1) // This can block before stream is streamed to 
    { 
     read = reader.Read(buffer, 0, 4096); 
     data = Server.ClientEncoding.GetBytes(buffer, 0, read); 
     this.clientStream.Write(data, 0, data.Length); //ClientStream is a NetworkStream 
    } 
} 
catch (Exception err) 
{ 
     Utilities.ConsoleOut(string.Format("StdOut err for client {0} -- {1}", this.clientID, err)); 
     this.ShutdownClient(true); 
} 

このコードブロックは、現在の背景ではない1つのスレッドで実行されます。 StandardErrorストリームにも同様のスレッドがあります。私はOutputDataReceivedとErrorDataReceivedをリッスンする代わりにこのメソッドを使用しています。なぜなら、これらのイベントが常に正しく発生するとは限らないMonoに問題があったからです。このメソッドでは、順次。

Trueを指定したShutdownClientは、単に両方のスレッドを強制終了しようとします。残念ながら、私がこの作業を行うために見つけた唯一の方法は、stdErrThreadおよびstdOutThreadオブジェクトに対して割り込みを使用することです。理想的にはピークがブロックされず、手動リセットイベントを使用してstdOutまたはstdInの新しいデータをチェックし、イベントが反転されたときにただ死ぬことができます。

私はこれを行うのが最善の方法だとは思っていません。割り込みを使用せずにこれを実行する方法はありますか?

私は、私のログで、Utlities.ConsoleOutの内部にスローされたThreadInterruptExceptionが見つからなかったので、私は変更したいと思います。これは、静的変数がtrueの場合はSystem.Console.Writeを実行しますが、このブロックがどこかにあると思います。

編集:

これらのスレッドは、要求に応じて、サーバによって一斉に発売された親スレッドの一部です。したがって、StdOutおよびStdErrスレッドをバックグラウンドに設定してアプリケーションを強制終了することはできません。私はメインサーバーから親スレッドを削除することができますが、これはPeekブロックでもスティッキーになります。

これがサーバーであることに関する情報を追加しました。

また、私はクエリのためのより良いキューイング方法を実現し始めています究極のソリューションかもしれません。

答えて

0

なぜ、両方のスレッドをバックラウンドに設定してアプリを強制終了しないのですか?両方のスレッドの即時終了が発生します。

+0

他の人が簡単に見ることができますので、私は元の質問に編集としてこれを追加します。しかし、これは、2つのストリームスレッドを使用して、時には何百ものプロセスを生成するサーバーです。アプリケーション全体が死ぬことはありません。メインサーバーにこの子スレッドを強制終了するように指示することができましたが、Peekブロッキングのために再びスティッキーになります。 – Mark

-1

ピークを取り除き、以下のメソッドを使用してプロセス出力ストリームから読み込みます。 ReadLine()は、プロセスが終了するとnullを返します。このスレッドにあなたの呼び出し元のスレッドに参加するには、プロセスが終了するのを待つか、自分でプロセスを終了させて​​ください。 ShutdownClient()は、他のスレッドがStdOutまたはStdErrを読み出すプロセスも終了させるプロセスをKill()する必要があります。

private void ReadToEnd() 
    { 
     string nextLine; 
     while ((nextLine = stream.ReadLine()) != null) 
     { 
      output.WriteLine(nextLine); 
     } 
    } 
+0

これは理にかなっていて、Peekを避けることができますが、ReadLineはまだブロックされています。残念ながら、これは、両方のスレッドを監視する必要があり、両方のスレッドが停止するまでリソースを解放できないことを意味します。わずか30プロセスであっても、この強制終了操作には200ミリ秒以上かかる場合があります。また、WriteLineが返ってきたときに両方のスレッドが終了するまで、NetworkStream接続を閉じることができないので、Writeは発生しません。 最終的に非常にまれなThreadInterrupedExceptionをスローして捕まえることは、発生する可能性のあるNullExceptionsとSocketExceptionsよりも処理することが少なくなります。 – Mark

+1

上記のReadToEnd()メソッドでNetworkStreamに書き込むのではなく、キューに書き込みます。だから、行が読み込まれた後、それはStdOutかStdErrかどうかを示す(直接必要な情報に応じてこれを調整します)、直接または別のオブジェクトの中のキューに入れられます。次に、キューから読み込んでNetworkStreamに書き込む別のスレッドがあります。明らかに、すべてがロックなどでスレッドセーフである必要があります.Withandandleを使用すると、スレッドの1つがキューに追加されるとすぐに、コンシューマスレッドがネットワークに書き込むようにWaitHandle.Set()を通知します。 – Kevin

+0

ああ、それは実際にはかなり良いアイデアです。私はプロデューサー/消費者型のことをやったことは考えていませんでした。これは間違いなく私がやっているよりもはるかに安全です。私はちょうど読者に書いてもらうことに夢中になった。 これは、データがないためにReadLineブロックが処理されても処理されません。残念なことに、.NETでは非ブロッキングのReadメソッドを提供していません。データがない場合は、ReadメソッドまたはPeekがブロックされ、Interruptでブロックを解除することができます。また、リーダーのときにnull例外がスローされることもあります。 1つの例外を別のものに交換することは意味がありません。 – Mark

2

この完全な混乱は、Peekブロックということからわかります。フレームワークで根本的に壊れたものを修正しようとしていますが、それは決して簡単ではありません(つまり、汚れたハックではありません)。個人的には、私は問題の根本を解決するでしょう。それはブロック化Peekです。 MonoはMicrosoftの実装に従っていたため、同じ問題が発生します。

フレームワークのソースコードを変更するにはどうすれば問題を解決できるか正確に分かっていますが、この回避策は時間がかかり、時間がかかります。

しかしここになります。マイクロソフトが行うために必要なもの基本的に

は、両方の StreamReader(例えば PipeStreamReader)の専門タイプが割り当てられている Process.StartWithCreateProcessように standardOutputことや standardError変更です。この PipeStreamReader

、それらは前読み取ること、PeekNamedPipeが実際のPEEKを行うために呼び出されるように(即ち、第1 StreamReader仮想の両方のオーバーロードを変更する必要がある)の両方ReadBufferオーバーロードをオーバーライドする必要があります。現時点では、Peek()によって呼び出されるFileStream.Read()は、読み込み可能なデータがない場合にはパイプの読み込みをブロックします。 0バイトのFileStream.Read()はファイルでうまく機能しますが、パイプ上ではうまく機能しません。実際、.NETチームはパイプ文書の重要な部分を逃しました - PeekNamedPipe WinAPI。

関数は常にシングルスレッドのアプリケーションですぐに戻り、は何があっても...

PeekNamedPipe機能は、以下の例外を除いてのReadFile関数に似ていませんパイプ内のデータ。名前付きパイプハンドル(ブロッキングまたはノンブロッキング)の待機モードは、この関数には影響しません。

フレームワークでこの問題が解決されないこの時点での最善のことは、独自のプロセスクラスを展開することです(WinAPIの周りの薄いラッパーで十分です)。

+0

.NETチーム:あなたがこれを読んでいるなら、修正のために私のブログポストに向かいましょう。http://blog.zachsaw.com/2011/07/streamreaderpeek-can-block-another-net.html –

+0

この修正を.NETコアに提案しますか? – knocte

0

あなたはサーバーを構築しています。あなたはブロックしないようにしたい。明白な解決策は、非同期APIを使用することではありません。

var myProcess = Process.GetCurrentProcess(); 
StreamReader reader = myProcess.StandardOutput; 

char[] buffer = new char[4096]; 
byte[] data; 
int read; 

while (!myProcess.HasExited) 
{ 
    read = await reader.ReadAsync(buffer, 0, 4096); 
    data = Server.ClientEncoding.GetBytes(buffer, 0, read); 

    await this.clientStream.WriteAsync(data, 0, data.Length); 
} 

I/Oの仕事をしているスレッドを無駄にする必要はありませ:)

+0

なぜ4096で、他の数字ではないのですか? – knocte

+0

@knocte 4096は彼の例でOPが使用したものです。いくつかの数字にはいくつかの利点がありますが、それはかなり広い話題です。たとえば、2のべき乗のバッファは断片化をヒープするのには非常に面白いかもしれませんが、実際には.NETでI/Oを実行するときに問題になります。バッファが固定されているため、ヒープを圧縮できません。これは、バッファが必要なメモリの50%以上を消費しないようにすることができるので(もちろん、最適なバッファがなくなるまで)、バッファを事前に割り当てるときに特に便利です。しかし、ほとんどの場合、これは単なる伝統です。より良いバッファサイズがわからない限り、力はうまく機能します。 – Luaan

関連する問題