Implementar el árbitro de intercalación de CCR en f#
Pregunta
Quiero implementar el concepto de un puerto del marco CCR en F# (ya que CCR no es compatible oficialmente para .NET 4.0). Sé que uno puede usar el Procesador de buzones Clase en f# para hacer esto. Esto funciona perfectamente para simple Recibir Árbitros pero necesito el concepto de Entrelazar Árbitro, es decir, quiero controlar qué mensajes se procesan exclusivamente y cuáles se procesan simultáneamente. Hasta ahora no tengo idea de implementar esto en F# y estaría agradecido por su ayuda.
Solución
No estoy muy familiarizado con CCR, pero intentaré responder; entendiendo que es que entrelazarse se comporta un poco como ReaderWriterLock
. Es decir, puede especificar algunas operaciones que pueden ejecutarse en paralelo (lecturas) y algunas operaciones que son exclusivas (escrituras).
El siguiente agente es una forma de implementarlo (no probado, pero escriba verificaciones :-)). El agente expone dos operaciones destinadas al uso público. El último es interno:
type Message<'T> =
| PerformReadOperation of ('T -> Async<unit>)
| PerformWriteOperation of ('T -> Async<'T>)
| ReadOperationCompleted
Enviando al agente
PerformReadOperation
, le está dando una operación que debe ejecutarse (una vez) utilizando el estado y posiblemente en paralelo con otras operaciones de lectura.Enviando al agente
PerformWriteOperation
, le está dando una operación que calcula un nuevo estado y debe ejecutarse después de todas las operaciones de lectura completas. (Si estuviera trabajando con un estado inmutable, eso simplificaría las cosas, ¡no tendría que esperar hasta que los lectores completen! Pero la implementación a continuación implementa la espera).
El agente comienza con algún estado inicial:
let initial = // initial state
Y el resto del agente se implementa utilizando dos bucles:
let interleaver = MailboxProcessor.Start(fun mbox ->
// Asynchronously wait until all read operations complete
let rec waitUntilReadsComplete reads =
if reads = 0 then async { return () }
else mbox.Scan(fun msg ->
match msg with
| ReadOperationCompleted -> Some(waitUntilReadsComplete (reads - 1))
| _ -> None)
let rec readingLoop state reads = async {
let! msg = mbox.Receive()
match msg with
| ReadOperationCompleted ->
// Some read operation completed - decrement counter
return! readingLoop state (reads - 1)
| PerformWriteOperation(op) ->
do! waitUntilReadsComplete reads
let! newState = op state
return! readingLoop newState 0
| PerformReadOperation(op) ->
// Start the operation in background & increment counter
async { do! op state
mbox.Post(ReadOperationCompleted) }
|> Async.Start
return! readingLoop state (reads + 1) }
readingLoop initial 0)
Otros consejos
Solo para agregar a la solución sugerida por Tomas, en caso de que no desee exponer el mensaje "Readoperation se compleja" al consumidor del cuadro de correo (ya que este mensaje es interno y en la implementación actual puede ser enviado por cualquier consumidor del cuadro de correo) Se puede crear un cuadro de correo separado dentro de la función del procesador de la casilla de correo principal que aceptará dos mensajes: ReadoperationCompleted y WaitForReadCompleted (este se usará con PostandasynCreply por el cuadro de correo principal), ya que la respuesta a este mensaje solo vendrá cuando toda la lectura Las operaciones se completan. Además, el recuento de "lectura" representado por "lecturas" se trasladará a este nuevo buzón interno, ya que ese estado se encapsula con este buzón interno.