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

时间:2010-09-13 15:28:06

标签: events f# asynchronous

在F#中,我知道如何使用Async.AwaitEvent异步等待一个事件:

let test = async {
  let! move = Async.AwaitEvent(form.MouseMove)
  ...handle move... }

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

let! moveOrKeyDown = Async.AwaitEvent(form.MouseMove, form.KeyDown)

这个功能不存在,但有另一种方法吗?

4 个答案:

答案 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.mapEvent.merge

let eventOccurs e = e |> Event.map ignore
let mouseOrKey = Event.merge (eventOccurs frm.MouseMove) (eventOccurs frm.KeyDown)

然后您可以将Async.AwaitEvent用于此新活动。如果MouseMoveKeyDown具有相同的类型,则可以跳过Event.map步骤直接合并它们。

修改

但Tomas指出,您应该优先使用Observable组合器,而不是Event组合。