简体   繁体   中英

Thread-safe raising of F# events

I'm trying to do an F# async computation that calls an C# callback when ready. The code is the following:

type Worker() = 

    let locker = obj()
    let computedValue = ref None
    let started = ref false
    let completed = Event<_>()

    let doNothing() = ()

    member x.Compute(callBack:Action<_>) = 
        let workAlreadyStarted, action = 
            lock locker (fun () -> 
                match !computedValue with
                | Some value ->
                    true, (fun () -> callBack.Invoke value)
                | None ->
                    completed.Publish.Add callBack.Invoke
                    if !started then                            
                        true, doNothing
                    else
                        started := true
                        false, doNothing)
        action()
        if not workAlreadyStartedthen
            async {                

                // heavy computation to calc result
                let result = "result"

                lock locker (fun () -> 
                    computedValue := Some result
                    completed.Trigger result)
            } |> Async.Start

But there's a problem, I want to trigger the completed event outside the lock, but I want to make sure that the triggering is thread safe (Actually, in this small example I could just trigger the event outside the lock as I know no one else will subscribe to it, but that's not always the case).

In C# events this is very easy to accomplish:

    object locker = new object();
    event Action<string> MyEvent;

    void Raise()
    {
        Action<string> myEventCache;
        lock (locker)
        {
            myEventCache = MyEvent;
        }
        if (myEventCache != null)
        {
            myEventCache("result");
        }
    }

How can I do the equivalent with F# events, freezing the list of subscribers inside the lock but invoking it outside the lock?

This isn't as straightforward in F# because Event<_> doesn't expose its subscriber list, which is mutated by Add / Remove .

You can avoid this mutation by creating a new event for each handler.

let mutable completed = Event<_>()

//...

let ev = Event<_>()
let iev = ev.Publish
iev.Add(completed.Trigger)
iev.Add(callBack.Invoke)
completed <- ev

//...

let ev = lock locker <| fun () -> 
    computedValue := Some result
    completed
ev.Trigger(result)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM