I have started using Hopac as an alternative to Async/TPL and I love it. I understand basic usage, but some aspects are still not clear.
First, could we compare Alt
to F# lazy
, so that a job inside an Alt
is only evaluated on Alt.pick?
Second, is this implementation of AutoResetEvent correct and idiomatic for Hopac?
/// <summary>
/// MSDN: The AutoResetEvent class represents a local wait handle event that resets automatically
/// when signaled, after releasing a single waiting thread. An AutoResetEvent object is automatically
/// reset to non-signaled by the system after a single waiting thread has been released.
/// If no threads are waiting, the event object's state remains signaled.
///
/// Hopac's alternative to http://blogs.msdn.com/b/pfxteam/archive/2012/02/11/10266923.aspx
/// </summary>
type HopacAutoResetEvent (initialState : bool) =
// We will wait on take, and set with send
let setChannel : Ch<unit> = ch()
do if initialState then start <| Ch.send setChannel ()
new() = HopacAutoResetEvent(false)
member this.Wait(timeout:int) : Job<bool> =
let timedOut : Alt<bool> =
((float timeout) |> TimeSpan.FromMilliseconds |> Timer.Global.timeOut)
>>=? fun () -> Job.result false
let signaled = Ch.Alt.take setChannel >>=? fun () -> Job.result true
signaled <|> timedOut
// From docs, important for <|>:
// The given alternatives are processed in a left-to-right order with short-cut evaluation.
// In other words, given an alternative of the form first <|> second, the first alternative
// is first instantiated and, if it is pickable, is committed to and the second alternative
// will not be instantiated at all.
member this.Set() : Job<unit> =
// from MSDN: Also, if Set is called when there are no threads waiting and the EventWaitHandle
// is already signaled, the call has no effect.
// try take and send covers all cases
// if there was no waiters and state was signalled -> will steal the state and send it back immediately
// if there were waiting thread or state was not signaled -> there was no signal and we steal nothing, just signal
(Ch.Try.take setChannel) >>. Ch.send setChannel ()
Third, is this implementation of ManualResetEvent correct and idiomatic for Hopac?
/// <summary>
/// Hopac's alternative to http://blogs.msdn.com/b/pfxteam/archive/2012/02/11/10266920.aspx
/// </summary>
type HopacManualResetEvent (initialState : bool) =
[<VolatileFieldAttribute>]
let mutable state : bool = initialState
let setChannel : MChan<bool> = run <| Multicast.create ()
let lock = Lock.Now.create()
new() = HopacManualResetEvent(false)
member this.Wait() : Job<bool> =
let rec loop () =
job {
if state then return true
else
let! port = Multicast.port setChannel
let! res = (Multicast.recv port) // waiting here
if res then return true
else return! loop ()
}
loop ()
// From Multicast.fsi: **Sends** a message to all of the ports listening to the multicast channel.
// Send must mean the same as in Ch
member this.Set() : Job<unit> =
(Multicast.multicast setChannel true) // there could be no waiters
|>> (fun _ -> state <- true ) // in any case we set the state
>>% () // and return unit
|> (Lock.duringJob lock)
member this.Reset() : Job<unit> =
(Multicast.multicast setChannel false) // (redundant?) if there are takers, res in loop() will be false and loop will iterate
|>> (fun _ -> state <- false ) // in any case we set the state
>>% ()
|> (Lock.duringJob lock)
Cross-post: https://github.com/VesaKarvonen/Hopac/issues/26