2010-09-13 7 views
7

In F # Ich weiß, wie asynchron warten, für ein Ereignis Async.AwaitEvent mit:Warten auf jeden Fall mehrerer Ereignisse gleichzeitig in F #

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

nehme ich entweder für das MouseMove oder das KeyDown Ereignis warten soll. Ich möchte so etwas haben:

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

Diese Funktion existiert nicht, aber gibt es eine andere Möglichkeit, dies zu tun?

+4

Vorsicht! Wenn Sie Ereignisse verwenden, die mit 'Event.xyz'-Kombinatoren zusammen mit' AwaitEvent' und 'let!' Erstellt wurden, können Sie ein Speicherleck erstellen (wenn Sie in einer Schleife warten). Sie ** sollten ** immer 'Observable' anstelle von' Event' verwenden, wenn Sie Kombinatoren mit asynchronen Workflows kombinieren möchten. Sehen Sie meine Antwort für weitere Details ... –

Antwort

11
let ignoreEvent e = Event.map ignore e 

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

EDIT: eine andere Version, die ursprünglichen Arten

let merged = Event.merge (f.KeyDown |> Event.map Choice1Of2) (f.MouseMove |> Event.map Choice2Of2) 
Async.AwaitEvent merged 

EDIT 2 bewahrt: nach Kommentare von 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 primitiv Es kann aus here ("Reaktive Demos in Silverlight" von Tomas Petricek) entnommen werden.

+0

zu enthalten Cool, lernte ein wenig mehr F #. Vielen Dank. –

+3

Könnten Sie den Code ändern, um "Observable" anstelle von "Event" zu verwenden? (Die Verwendung von 'Event.xyz' in diesem Szenario kann zu Undichtigkeiten führen - siehe meine Antwort für weitere Informationen ...) –

+0

Sie sollten beachten, dass' Async.AwaitObservable' nicht in F # eingebaut ist und dass es eine Erweiterung in "Real" ist -Weltfunktionale Programmierung. " – gradbot

0

Sie können eine Kombination von Event.map und Event.merge verwenden:

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

Dann können Sie Async.AwaitEvent mit diesem neuen Ereignis verwenden. Wenn MouseMove und KeyDown denselben Typ hätten, könnten Sie den Schritt Event.map überspringen und sie einfach direkt zusammenführen.

EDIT

Aber Tomas weist darauf hin, sollten Sie die Observable combinators bevorzugt gegenüber den Event diejenigen verwenden.

+0

Dies funktioniert, aber jetzt verliere ich die Fähigkeit, etwas mit den Ereigniseigenschaften zu tun, weil das Ereignis vom Typ Einheit ist. Vielleicht muss ich einen ganz anderen Ansatz wählen, aber ich bin mir nicht sicher was. –

+1

Sie können Ereignisargumente auf Choice (oder einen anderen Typ) abbilden, anstatt sie zu ignorieren. Ich habe meine Antwort bearbeitet, um Probe für diese Version – desco

4

Um zu verstehen, was los ist, habe ich den Quellcode nach Event.map, Event.merge und Choice gesucht.

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 

Dies bedeutet, dass unsere Lösung 3 neue Ereignisse erstellt.

Wir könnten dies auf ein Ereignis reduzieren, indem wir eine eng gekoppelte Version dieses Bibliothekscodes erstellen.

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 

Und hier ist unser neuer Code.

async { 
    let merged = EventChoice.CreateChoice form.MouseMove form.KeyDown 
    let! move = Async.AwaitEvent merged 
} 
12

ich eine Implementierung eines Verfahrens, das Sie in der talk about reactive programming in Ihrer Probe verwenden, die ich in London hatte (es ist ein Download-Link am Ende der Seite). Wenn Sie an diesem Thema interessiert sind, können Sie das Gespräch auch nützlich finden :-).

Die Version, die ich verwende, nimmt IObservable statt IEvent (so der Name der Methode ist AwaitObservable).Es gibt einige schwerwiegende Speicherlecks bei Verwendung Event.merge (und andere Kombinatoren aus dem Event Modul) zusammen mit AwaitEvent, so sollten Sie stattdessen Observable.merge usw. und AwaitObservable verwenden.

Das Problem wird detaillierter beschrieben here (siehe Kapitel 3 für ein klares Beispiel). Kurz gesagt - wenn Sie Event.merge verwenden, wird ein Handler an das Quellenereignis angehängt (z. B. MouseDown), aber der Handler wird nicht entfernt, nachdem Sie mit AwaitEvent gewartet haben. Das Ereignis wird also nie entfernt - wenn Sie in einer mit codierten Schleife warten asynchroner Workflow, Sie fügen neue Handler hinzu (die nichts tun, wenn sie ausgeführt werden).

Eine einfache richtige Lösung (basierend auf welche desco veröffentlicht) würde wie folgt aussehen:

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: Sie können auch bei this article (16 basierend auf Kapitel aus meinem Buch) sehen möchten.

+0

Interessant. Es war tatsächlich beim Lesen von Kapitel 16 Ihres Buches, dass diese Frage aufkam. Ich dachte, es müsste einen Weg geben, auf dem F # Core irgendwie Unterstützung für dieses Szenario bot und es scheint so zu sein. In Abschnitt 3 des Artikels verstehe ich, was das Problem ist. Worüber ich nicht sicher bin, ob Observables die in Abschnitt 6 beschriebene Rückwärtsreferenzierung verwenden, um Speicherlecks zu verhindern? Oder benutzen sie eine andere Technik? –

+0

@Ronald: Observables verwenden eine andere Technik als die, die im referenzierten Artikel beschrieben wird. Kurz gesagt (momentan habe ich nicht viel Zeit) - wenn Sie anfangen, eine Observable zu hören (z. B. erstellt mit map), wird ein Token zurückgegeben, mit dem Sie sich von der ursprünglichen Ereignisquelle abmelden können. –

+0

@TomasPetricek - Der http://tomasp.net/academic/event-chaines/event-chains.pdf Link scheint jetzt gebrochen zu sein. Wurde das PDF an einen anderen Ort verschoben? – rmunn

Verwandte Themen