Стоит ли оборачивать TcpClient WriteAsync и ReadAsync в MailboxProcessor?
Резюме вопросов:
- Разве Stream.WriteAsync и Stream.ReadAsync не блокируют друг друга и могут работать параллельно на двух ядрах процессора?
- Есть ли способ уведомить, ждать, пока внешний источник уведомит в async{}?
- Что влияет на производительность MailboxProcessor как оболочки для TcpClient?
Некоторое объяснение:
Как я понимаю из исходного кода Stream.cs, асинхронные операции выполняются синхронно, а не полностью параллельно (если вы что-то читаете, он блокирует другие попытки чтения до завершения, а также блокирует записи, аналогично другим способом - запись блокирует попытки чтения и записи) единственное преимущество в том, что он не блокирует поток, где вы вызываете writeAsync/readAsync
Поэтому я обернул TcpClient в MailboxProcessor, который принимает
type AgentRequest<'a> =
| Write of 'a
| Read of int * AsyncReplyChannel<'a>
Цель этого состояла в том, чтобы переподключить клиента в случае сбоя подключения, и в течение этого времени мы не хотим пытаться что-либо читать или писать. Это также возможно с техникой синхронизации потоков, но это требует гораздо больше кода.
Я не смог определить, когда я могу сравнить решение, но есть ли какие-либо критерии внутренних компонентов MailboxProcessor, чтобы увидеть, как это повлияет на tcp-соединение (если влияние на производительность невелико с точки зрения времени запроса / ответа)
Также это делается для отправки запросов на сервер, который гарантирует порядок ответов, совпадающих с полученными запросами, но я не могу положиться на Write requestA
-> Read responseA
чтобы прочитать правильный ответ: шаг1: написать запрос A прочитать ответ A шаг2: написать запрос B прочитать ответ B
Очередь: ['запрос на запись A'; 'написать запрос B'; 'прочитать ответ B'; 'read responseA'] И это приведет к тому, что responseA будет возвращен в thread2, а responseB - в thread1.
Хорошо, что запрос-ответ соотносится с идентификатором, установленным в запросе. Таким образом, есть некоторые решения для этого протокола, которые хранят Dictionary<id, TaskCompletionSource>
, Это позволяет дождаться завершения задачи (TaskCompletionSource set result) и продолжить работу с результатом. Результат устанавливается отдельным потоком, который продолжает читать ответы из потока tcp и отображать их по идентификатору.
В F# гораздо приятнее использовать Async вместо Task, поэтому я вижу способ отображения ответов на правильные запросы - хранить Dictionary<id, response -> unit>
Или я могу иметь только 1 сообщение для MailboxProcessor, например 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*) }