2013-02-25 52 views
3

我试图做一个F#异步计算,在准备好时调用C#回调函数。代码如下:F#事件的线程安全提升

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 

但是有一个问题,我想触发锁外完成的事件,但我想,以确保触发是线程安全的(实际上,在这个小例子我可以只是触发锁定以外的事件,因为我知道没有其他人会订阅它,但情况并非总是如此)。

在C#中的事件,这是很容易做到:

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

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

我可怎么办与F#事件的等效,冻结锁内的用户的列表中,但调用它锁外面?

+0

您确定C#冻结了用户列表吗?你不是在参考这个活动吗? – Daniel 2013-02-25 15:04:31

+0

无论何时向事件添加新的处理程序,都会创建一个新的委托值并替换以前的值,因此,如果在复制代理的值时添加了另一个处理程序,则它将不会链接到制作的副本 – 2013-02-25 15:09:31

+0

如果您需要访问订阅者列表,那么您可以使用自己的'IEvent <_>'实现,而不是使用'Event <_> .Publish'提供的实现。 – kvb 2013-02-25 19:57:46

回答

1

这是因为Event<_>不公开它的订户名单,这是由Add/Remove突变不是F#的那么简单。

您可以通过为每个处理程序创建一个新事件来避免此突变。

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)