3

TCP 接続の読み取り/書き込みにNetworkStream(またはおそらく)を使用したい。Socket複数のスレッドを処理したり、ネットワーク操作のブロックで一部のスレッドが停止する可能性がある場合にプログラムを停止する方法の問題に対処したりする必要がないように、非ブロック操作を使用したいと考えています。

NetworkStream.Readのドキュメントは、それが非ブロッキングであることを暗示しています (「読み取りに使用できるデータがない場合、Read メソッドは 0 を返します」)。

しかし、書くことはどうですか?Microsoft には、このテーマに関する通常の低品質のチュートリアルAsyncCallbackがあり、すべての操作に対して面倒なメソッドを作成することを提案しています。このコールバック内のコードのポイントは何ですか?

client.BeginSend(byteData, 0, byteData.Length, SocketFlags.None,
                 new AsyncCallback(SendCallback), client);
    ...
private static void SendCallback(IAsyncResult ar)
{
    try {
        Socket client = (Socket)ar.AsyncState;

        int bytesSent = client.EndSend(ar);

        Console.WriteLine("Sent {0} bytes to server.", bytesSent);
        // Signal that all bytes have been sent.

        sendDone.Set();
    }
    catch (Exception e)
    {
        Console.WriteLine(e.ToString());
    }
}

のドキュメントにAsyncCallbackは、「非同期操作が完了したときに呼び出されるコールバック メソッド」であると書かれています。操作が既に完了している場合、なぜ電話する必要があるのSocket.EndSend(IAsyncResult)でしょうか? 一方、のドキュメントにNetworkStream.BeginWriteは、「デリゲートを実装するコールバック メソッドを作成し、AsyncCallbackその名前をBeginWriteメソッドに渡す必要があります。少なくとも、状態パラメーターには . が含まれている必要がありますNetworkStream。」と書かれています。

しかし、なぜ「しなければならない」のでしょうか。IAsyncResultどこかに保管できないのでしょうか...

_sendOp = client.BeginSend(message, 0, message.Length, SocketFlags.None, null, null);

...そして定期的に完了したかどうかを確認しますか?

if (_sendOp.IsCompleted)
    // start sending the next message in the queue

(これはポーリングを意味することは知っていますが、ほとんどの場合アクティブであると予想されるサーバーにあり、アイドル時にいくつかの Thread.Sleeps を計画しています。)

別の問題。送信するメッセージは、ヘッダー配列と本文配列に分割されます。2 つの BeginWrite を続けて発行できますか?

IAsyncResult ar1 = _stream.BeginWrite(header, 0, header.Length, null, null);
IAsyncResult ar2 = _stream.BeginWrite(msgBody, 0, msgBody.Length, null, null);

また、Socket と NetworkStream のどちらを使用するかについての意見も歓迎します。

4

2 に答える 2

3

の経験はありませんが、非同期操作NetworkStreamについては次のように言えます。Socket

  • End*これにより、非同期操作で使用される OS リソースがクリーンアップされ、操作の結果を取得できるようになるため (その結果が成功したか失敗したかだけであっても)。MSDN には、この一般的な非同期パターンの概要が記載されています。
  • 特定のオブジェクトを状態パラメーターとして渡す必要はありません。そのドキュメントが作成されたとき、それを渡すことが、完了デリゲートのスコープ内にオブジェクトを保持する最も簡単な方法でした。最近では、ラムダ式では、その慣行はほとんど一般的ではありません。
  • ソケットへの書き込みをキューに入れることができ、ほとんどの条件下でこれは機能します。ただし、書き込みの 1 つが部分的にしか完了しない可能性があります (これが起こるのを見たことはありませんが、理論的には可能です)。私はこれを何年も行っていましたが、もうお勧めしません。

ポーリングの代わりに完了デリゲートを使用することをお勧めします。より使いやすいラッパーが必要な場合は、Nito.Async.SocketsBeginを試すことができます。これは/操作をラップしEnd、単一のスレッド (UI スレッドなど) を介してすべてをシリアル化します。

PSブロッキング操作を停止したい場合は、別のスレッドからソケットを閉じることができます。これにより、操作がエラーで完了します。ただし、ブロッキング ソケット操作の使用はお勧めしません。

于 2011-04-27T00:53:55.133 に答える
1

NetworkStream.Read のドキュメントは、それが非ブロッキングであることを暗示しています (「読み取りに使用できるデータがない場合、Read メソッドは 0 を返します」)。

読み取りに使用できるデータが存在することを決して計画しない限り、それが非ブロッキング操作をどのように意味するのかわかりません。読み取り可能なデータがある場合、データの読み取りにかかる間、操作はブロックされます。


しかし、書くことはどうですか?Microsoft には、このテーマに関する通常の低品質のチュートリアルがあり、操作ごとに扱いにくい AsyncCallback メソッドを作成することを提案しています。このコールバック内のコードのポイントは何ですか?

要点は、さらに面倒なスレッド管理コードを自分で作成しないようにすることです。この例はかなりばかげていますが、マルチスレッドが進むにつれて、AsyncCallback はほぼ単純になります。この例では、コールバック ルーチンはManualResetEvent( sendDone.Set()) を通知します。これは、ポーリングを回避する 1 つの方法です。その上で待機する別のスレッドがあり、ManualResetEvent他の何かがそのSet()メソッドを呼び出すまでブロックします。しかし、待ってください。最初に AsyncCallback を使用して、独自のスレッド管理を作成しないようにすることが主な目的ではありませんでしたか? では、なぜ待機中のスレッドがあるのですか?ManualResetEvent? より良い例は、AsyncCallback メソッドで実際に何かを行うことを示していますが、フォーム コントロールなどの UI 要素と対話している場合は、コントロールのInvokeメソッドを直接操作するのではなく、使用する必要があることに注意してください。


操作が既に完了している場合、Socket.EndSend(IAsyncResult) を呼び出す必要があるのはなぜですか?

Methodの MSDN ドキュメントStream.BeginWriteから:

現在のメソッドによって返された IAsyncResult を EndWrite に渡して、書き込みが完了し、リソースが適切に解放されるようにします。EndWrite は、BeginWrite を呼び出すたびに 1 回呼び出す必要があります。これは、BeginWrite を呼び出したのと同じコードを使用するか、BeginWrite に渡されたコールバックで行うことができます。非同期書き込み中にエラーが発生した場合、このメソッドによって返された IAsyncResult で EndWrite が呼び出されるまで、例外はスローされません。

I/O 操作で何が起こるかわかりません。また、スレッド プール内の別のスレッドで実行が行われるため、非同期操作では通常のエラー処理を行うことができません。したがって、呼び出しのポイントは、AsyncCallback メソッド内に try/catch ブロックを配置して、EndWriteエラーを処理する機会を与えることです。EndWrite


IAsyncResult をどこかに保存して、完了したかどうかを定期的に確認することはできませんか?

すべてのスレッド管理と安全性の問題を処理して、異なるスレッドが同じ IAsyncResult を同時に処理しようとしないようにすることもできますが、それは AsyncCallback を記述するよりもはるかに面倒なように思えます。繰り返しになりますが、非同期メソッドを使用するポイントは、スレッド化の問題にできるだけ対処しなくて済むようにすることです。


別の問題。送信するメッセージは、ヘッダー配列と本文配列に分割されます。2 つの BeginWrite を続けて発行できますか?

私はこれを試したことはありませんが、うまくいくかどうかはわかりません。また、実際には NetworkStream クラスの特定の実装に依存しています。実行を試みる前に、最初のリクエストが完了するまで 2 番目のリクエストが待機する可能性がありますが、例外がスローされるか、相手側で破損したデータが取得されると感じています。2 つの別々のデータ ストリームを同時に送信したい場合は、2 つの別々の NetworkStream を使用するのが最善の方法だと思います。

于 2011-04-27T00:19:07.483 に答える