在F#中,我知道如何使用Async.AwaitEvent
异步等待一个事件:
let test = async {
let! move = Async.AwaitEvent(form.MouseMove)
...handle move... }
假设我要等待MouseMove
或KeyDown
事件。我想要这样的东西:
let! moveOrKeyDown = Async.AwaitEvent(form.MouseMove, form.KeyDown)
这个功能不存在,但有另一种方法吗?
答案 0 :(得分:12)
我使用了您在伦敦的talk about reactive programming示例中使用的方法的实现(页面底部有一个下载链接)。如果您对此主题感兴趣,您可能会发现该演讲也很有用: - )。
我使用的版本采用IObservable
代替IEvent
(因此方法的名称为AwaitObservable
)。使用Event.merge
(以及Event
模块中的其他组合符)和AwaitEvent
时,有一些严重的内存泄漏,因此您应该使用Observable.merge
等等而AwaitObservable
。
更详细地描述了问题here(参见第3节的清晰示例)。简单地说 - 当你使用Event.merge
时,它会将一个处理程序附加到源事件(例如MouseDown
),但在使用AwaitEvent
完成等待后它不会删除处理程序,所以事件是永远不会删除 - 如果你继续在使用异步工作流编码的循环中等待,你会不断添加新的处理程序(在运行时不做任何事情)。
一个简单正确的解决方案(基于 desco 发布的内容)将如下所示:
let rec loop () = async {
let e1 = f.KeyDown |> Observable.map Choice1Of2
let e2 = f.MouseMove |> Observable.map Choice2Of2
let! evt = Observable.merge e1 e2 |> Async.AwaitObservable
// ...
return! loop() } // Continue looping
顺便说一句:您可能还想看this article(根据我的书第16章)。
答案 1 :(得分:11)
let ignoreEvent e = Event.map ignore e
let merged = Event.merge (ignoreEvent f.KeyDown) (ignoreEvent f.MouseMove)
Async.AwaitEvent merged
编辑:保留原始类型的另一个版本
let merged = Event.merge (f.KeyDown |> Event.map Choice1Of2) (f.MouseMove |> Event.map Choice2Of2)
Async.AwaitEvent merged
编辑2 :根据 Tomas Petricek的评论
let e1 = f.KeyDown |> Observable.map Choice1Of2
let e2 = f.MouseMove |> Observable.map Choice2Of2
let! evt = Observable.merge e1 e2 |> Async.AwaitObservable
AwaitObservable 原语可以从here(Tomas Petricek的'Silverlight中的'Reactive demos')中获取。
答案 2 :(得分:4)
为了理解发生了什么,我将源代码查找到Event.map,Event.merge和Choice。
type Choice<'T1,'T2> =
| Choice1Of2 of 'T1
| Choice2Of2 of 'T2
[<CompiledName("Map")>]
let map f (w: IEvent<'Delegate,'T>) =
let ev = new Event<_>()
w.Add(fun x -> ev.Trigger(f x));
ev.Publish
[<CompiledName("Merge")>]
let merge (w1: IEvent<'Del1,'T>) (w2: IEvent<'Del2,'T>) =
let ev = new Event<_>()
w1.Add(fun x -> ev.Trigger(x));
w2.Add(fun x -> ev.Trigger(x));
ev.Publish
这意味着我们的解决方案是创建3个新事件。
async {
let merged = Event.merge
(f.KeyDown |> Event.map Choice1Of2)
(f.MouseMove |> Event.map Choice2Of2)
let! move = Async.AwaitEvent merged
}
我们可以通过制作这个库代码的紧密耦合版本将其减少为一个事件。
type EventChoice<'T1, 'T2> =
| EventChoice1Of2 of 'T1
| EventChoice2Of2 of 'T2
with
static member CreateChoice (w1: IEvent<_,'T1>) (w2: IEvent<_,'T2>) =
let ev = new Event<_>()
w1.Add(fun x -> ev.Trigger(EventChoice1Of2 x))
w2.Add(fun x -> ev.Trigger(EventChoice2Of2 x))
ev.Publish
这是我们的新代码。
async {
let merged = EventChoice.CreateChoice form.MouseMove form.KeyDown
let! move = Async.AwaitEvent merged
}
答案 3 :(得分:0)
您可以结合使用Event.map
和Event.merge
:
let eventOccurs e = e |> Event.map ignore
let mouseOrKey = Event.merge (eventOccurs frm.MouseMove) (eventOccurs frm.KeyDown)
然后您可以将Async.AwaitEvent
用于此新活动。如果MouseMove
和KeyDown
具有相同的类型,则可以跳过Event.map
步骤直接合并它们。
修改强>
但Tomas指出,您应该优先使用Observable
组合器,而不是Event
组合。