Better understanding of F# Hopac library

708 views Asked by At

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

0

There are 0 answers