2013-02-25 84 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)