Summary of questions:
- Are Stream.WriteAsync and Stream.ReadAsync not blocking each other and can run on 2 cpu cores fully in parallel?
- Is there a way to notify wait for external source to notify in async{} ?
- What is performance impact of MailboxProcessor as a wrapper for TcpClient ?
Some explanation:
As I understand from source code of Stream.cs async operations done synchronously and not completely in parallel (if you read something, it blocks other read attempts until finished and also blocks write attemts, same in other way - write blocks read and write attempts), the only benefit that it doesn't block thread where you call writeAsync/readAsync
So I have wrapped TcpClient into MailboxProcessor that accepts
type AgentRequest<'a> =
| Write of 'a
| Read of int * AsyncReplyChannel<'a>
Purpose of it was to reconnect client if connection failed, and during this time we don't want to attempt to read or write anything. This is also possible with thread synchronization techniques, but that require lot more code.
I didn't get to position when I can benchmark the solution, but is there any benchmarks of MailboxProcessor internals to see what impact will it make to tcp connection (if performance impact is small in terms of whole request/response time)
Also this is created to send requests to server that guarantees order of responses to be same as requests received
But I can't rely on Write requestA
-> Read responseA
to read the correct response:
tread1:
Write requestA
Read responseA
tread2:
Write requestB
Read responseB
Queue: ['write requestA'; 'write requestB'; 'read responseB'; 'read responseA'] And this will lead that responseA will be returned to thread2 and responseB to thread1.
Good thing that request-response are correlated by id set in request.
So there are some solutions for this protocol that store Dictionary<id, TaskCompletionSource>
. This makes it possible to wait till Task is finished (TaskCompletionSource set result) and then proceed with result. Result set by separate single thread that keep reading responses from tcp stream and mapping them by id.
In F# it's much beautiful to use Async instead of Task, so the way I see how to map responses to correct requests is to store Dictionary<id, response -> unit>
Or I can have only 1 message for MailboxProcessor like Send request
and do not separate write request from read response.
Is there any other way similar to TaskCompletionSource but for Async, so I could have:
// 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*) }