Events 在F中同时等待多个事件中的任何事件#

Events 在F中同时等待多个事件中的任何事件#,events,f#,asynchronous,Events,F#,Asynchronous,在F#中,我知道如何使用以下命令异步等待一个事件: 假设我想等待事件或事件。我想要这样的东西: let! moveOrKeyDown = Async.AwaitEvent(form.MouseMove, form.KeyDown) let rec loop () = async { let e1 = f.KeyDown |> Observable.map Choice1Of2 let e2 = f.MouseMove |> Observable.map Choice2Of2

在F#中,我知道如何使用以下命令异步等待一个事件:

假设我想等待事件或事件。我想要这样的东西:

let! moveOrKeyDown = Async.AwaitEvent(form.MouseMove, form.KeyDown)
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

此函数不存在,但是否有其他方法可以执行此操作?

您可以结合使用
事件.map
事件.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指出,您应该优先使用
可观察的
组合词,而不是
事件
组合词

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的评论


可观察原语可以从Tomas Petricek的“Silverlight中的反应演示”中获取

为了理解发生了什么,我查阅了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
我们可以通过制作此库代码的紧密耦合版本将此事件减少为一个事件

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

我使用了一个方法的实现,您可以在我在伦敦看到的示例中使用该方法(页面底部有一个下载链接)。如果你对这个话题感兴趣,你可能会发现这个讲座也很有用:-)

我使用的版本采用了
ioobservable
而不是
IEvent
(因此方法的名称是
AwaitObservable
)。使用
事件时,存在一些严重的内存泄漏

对该问题进行了更详细的描述(请参见第3节以获取清晰的示例)。简而言之-当您使用
Event.merge
时,它会将处理程序附加到源事件(例如
MouseDown
),但在您使用
waitevent
完成等待后,它不会删除处理程序,因此事件永远不会被删除-如果您在使用异步工作流编码的循环中继续等待,则会继续添加新的处理程序(运行时不做任何事情)

一个简单正确的解决方案(基于desco发布的内容)如下所示:

let! moveOrKeyDown = Async.AwaitEvent(form.MouseMove, form.KeyDown)
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

顺便说一句:您可能还想看看(基于我的书中的第16章)。

这是可行的,但现在我失去了对事件属性执行任何操作的能力,因为事件的类型是
unit
。也许我需要采取完全不同的方法,但我不确定是什么。您可以选择映射事件参数(或其他类型)不要忽略它。我已经编辑了我的答案,以包含此版本的示例。小心!当您将使用
Event.xyz
组合器创建的事件与
waitevent
let!
一起使用时,您可以创建内存泄漏(在循环中等待时)如果你想把组合器和异步工作流结合起来,你应该总是使用<代码>可观察的模块,而不是组合代码>事件/代码>。请参阅我的答案以获得更多的细节……你能考虑改变代码来使用<代码>可观察的< /代码>而不是<代码>事件< /代码>吗?(在此场景中使用
Event.xyz
可能会导致泄漏-有关更多信息,请参阅我的答案…)您可能需要注意的是
Async.AwaitObservable
并没有内置在F#中,它是“真实世界函数编程”中的一个扩展AwaitObservable现在出现在fsharpx中-很有趣。事实上,在阅读你的书第16章时,这个问题出现了。我认为一定有一种方式,F#Core以某种方式为这个场景提供了支持,它似乎确实提供了支持。通过阅读文章的第3节,我理解了问题所在。我不确定的是观察者使用第6节中描述的反向引用技术来防止内存泄漏?还是他们使用了其他技术?@Ronald:观察者使用了一种与参考文献中描述的不同的技术。简单地说(我现在没有太多时间)-当你开始听一个观察者时(例如,使用
map
创建),它返回一个令牌,可用于从原始事件源注销。@TomasPetricek-链接现在似乎已断开。该PDF是否已移到其他位置?
async {
    let merged = EventChoice.CreateChoice form.MouseMove form.KeyDown
    let! move = Async.AwaitEvent merged
}
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