2010-09-13 63 views
7

在F#我知道如何異步等待一個事件中使用Async.AwaitEvent爲:在F#中同時等待多個事件中的任何事件

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

假設我要等待要麼MouseMoveKeyDown事件。我想有這樣的事情:

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

此功能不存在,但有另一種方法來做到這一點?

+4

小心!當你將使用'Event.xyz'組合子創建的事件與'AwaitEvent'和'let!'一起使用時,你可以創建一個內存泄漏(當你在循環中等待時)。如果你想把組合器和異步工作流結合起來,你應該總是**使用'Observable'模塊來代替'Event'。查看我的答案以獲取更多詳細信息... – 2010-09-13 21:56:15

回答

11
let ignoreEvent e = Event.map ignore e 

let merged = Event.merge (ignoreEvent f.KeyDown) (ignoreEvent f.MouseMove) 
Async.AwaitEvent merged 

編輯:另一個,可以保留原始類型

​​

編輯2版本:根據托馬斯Petricek的評論

let e1 = f.KeyDown |> Observable.map Choice1Of2 
let e2 = f.MouseMove |> Observable.map Choice2Of2 
let! evt = Observable.merge e1 e2 |> Async.AwaitObservable 

AwaitObservable primitiv e可以從here(Tomas Petricek的'Silverlight中的反應式演示')中獲得。

+0

酷,學到了更多的F#。謝謝。 – 2010-09-13 17:21:29

+3

你可以考慮改變代碼來使用'Observable'而不是'Event'嗎? (在這種情況下使用'Event.xyz'可能導致泄漏 - 請參閱我的回答以獲取更多信息...) – 2010-09-13 22:00:13

+0

您可能想要注意'Async.AwaitObservable'沒有內置到F#中,並且它是「Real - 世界功能編程「。 – gradbot 2010-09-14 19:04:22

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步驟,直接將其合併。

編輯

但在托馬斯指出,你應該使用偏好Observable組合子到Event的。

+0

這可行,但現在我放棄了對事件屬性進行任何操作的能力,因爲事件的類型爲「unit」。也許我需要採取完全不同的方法,但我不確定是什麼。 – 2010-09-13 16:09:57

+1

您可以映射Choice(或其他類型)上的事件參數,而不是忽略它。我編輯了我的答案,包含此版本的示例 – desco 2010-09-13 16:54:55

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 
} 
12

我用你的樣品在talk about reactive programming,我在倫敦使用方法的實現(沒有在頁面底部的下載鏈接)。如果你對這個話題感興趣,你可能會覺得這個講話很有用:-)。

我使用的版本採用IObservable而不是IEvent(所以方法的名稱是AwaitObservable)。有一些嚴重的內存泄露使用時Event.merge(從Event模塊等組合子)與AwaitEvent在一起,所以你應該使用Observable.merge等和AwaitObservable代替。

該問題更詳細地描述here(有關清晰示例,請參閱第3節)。簡而言之 - 當您使用Event.merge時,它將源處理程序附加到源事件(例如MouseDown),但在您使用AwaitEvent完成等待後,它不會刪除處理程序,因此事件永遠不會被刪除 - 如果您繼續等待循環編碼異步工作流程,您不斷添加新的處理程序(運行時不會執行任何操作)。

一個簡單的正確的解決方案(基於什麼德斯科公佈)應該是這樣的:

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 

BTW:你可能也想看看this article(基於我的書第16章)。

+0

有趣。實際上,在閱讀你的書的第16章時,這個問題出現了。我認爲必須有一種方式,F#Core以某種方式爲這種情況提供了支持,而且看起來確實如此。通過閱讀本文的第3部分,我明白了問題所在。我不確定的是,observables是否使用了第6節中描述的反向引用技術來防止內存泄漏?或者他們使用其他技術? – 2010-09-14 08:10:38

+0

@Ronald:觀察者使用與參考文獻中描述的技術不同的技術。簡單地說(我現在沒有太多時間) - 當你開始收聽一個可觀察的數據(例如使用'map'創建的數據)時,它會返回一個可用於從原始事件源註銷的標記。 – 2010-09-14 08:26:55

+0

@TomasPetricek - http://tomasp.net/academic/event-chains/event-chains.pdf鏈接現在似乎已被破壞。該PDF已被移至別處嗎? – rmunn 2016-05-02 05:53:05