我正在尝试进行F#异步计算,在准备就绪时调用C#回调。代码如下:
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 :(得分:1)
这在F#中并不那么简单,因为Event<_>
没有公开其订阅者列表,该列表由Add
/ Remove
进行了突变。
您可以通过为每个处理程序创建一个新事件来避免此突变。
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)