質問の要約:
- Stream.WriteAsync と Stream.ReadAsync は互いにブロックせず、2 つの CPU コアで完全に並列に実行できますか?
- 外部ソースが async{} で通知するのを待つことを通知する方法はありますか?
- TcpClient のラッパーとしての MailboxProcessor のパフォーマンスへの影響は何ですか?
いくつかの説明:
Stream.cs非同期操作のソースコードから理解できるように、完全に並列ではなく同期的に行われます(何かを読み取ると、他の読み取り試行が完了するまでブロックされ、書き込み試行もブロックされます。他の方法と同じです-書き込みブロックの読み取りと書き込みの試行) 、 writeAsync/readAsync を呼び出すスレッドをブロックしないという唯一の利点
だから私は TcpClient を受け入れる MailboxProcessor にラップしました
type AgentRequest<'a> =
| Write of 'a
| Read of int * AsyncReplyChannel<'a>
その目的は、接続が失敗した場合にクライアントを再接続することでした。この間、何も読み書きを試みたくありません。これは、スレッド同期手法でも可能ですが、より多くのコードが必要になります。
ソリューションのベンチマークを実行できる時期を特定できませんでしたが、MailboxProcessor 内部のベンチマークで、tcp 接続にどのような影響があるかを確認できますか (要求/応答時間全体に関してパフォーマンスへの影響が小さい場合)。
また、これは、応答の順序が受信した要求と同じであることを保証する要求をサーバーに送信するために作成されますが、信頼することはできませんWrite requestA
->Read responseA
正しい応答を読み取ることはできません。
キュー: ['write requestA'; '書き込みリクエスト B'; 'responseB を読む'; 'read responseA'] そして、これにより、responseA がスレッド 2 に返され、responseB がスレッド 1 に返されます。
リクエストに設定されたIDによってリクエストとレスポンスが関連付けられるのは良いことです。そのため、このプロトコルには を保存するソリューションがいくつかありますDictionary<id, TaskCompletionSource>
。これにより、Task が終了するまで待機し (TaskCompletionSource が結果を設定)、結果に進むことができます。結果は、TCP ストリームから応答を読み取り、ID でマッピングし続ける個別の単一スレッドによって設定されます。
F# では、Task の代わりに Async を使用するのが非常に美しいので、応答を正しい要求にマップする方法を確認する方法は、保存するDictionary<id, response -> unit>
ことSend request
です。
TaskCompletionSource に似た他の方法がありますが、Async の場合は次のようになります。
// instead of
async {
do! send request
sharedDictionary.Add(request.id, fun resp -> () (*do something with this response*) )
read() }
// do something like
async {
do! send request
let! response = read request.id
(*do something with this response*) }