Ожидать любое событие из нескольких событий одновременно в F # - PullRequest
7 голосов
/ 13 сентября 2010

В F # я знаю, как асинхронно ждать одного события, используя Async.AwaitEvent:

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

Предположим, я хочу дождаться события MouseMove или KeyDown. Я хотел бы иметь что-то вроде этого:

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

Эта функция не существует, но есть ли другой способ сделать это?

Ответы [ 4 ]

12 голосов
/ 14 сентября 2010

Я использовал реализацию метода, который вы используете в своем примере в разговоре о реактивном программировании , который у меня был в Лондоне (в нижней части страницы есть ссылка для скачивания). Если вам интересна эта тема, вам также может пригодиться этот доклад: -).

Используемая версия использует IObservable вместо IEvent (поэтому имя метода AwaitObservable). Имеются некоторые серьезных утечек памяти при использовании Event.merge (и других комбинаторов из модуля Event) вместе с AwaitEvent, поэтому вы должны использовать Observable.merge и т. Д. И AwaitObservable.

Проблема описана более подробно здесь (см. Четкий пример в разделе 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

Кстати: вы также можете посмотреть эту статью (основываясь на главе 16 из моей книги).

11 голосов
/ 13 сентября 2010
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 : согласно комментариям Томас Петричек

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

AwaitObservable Примитив можно взять из здесь («Реактивные демонстрации в Silverlight» Томаса Петричека).

4 голосов
/ 13 сентября 2010

Чтобы понять, что происходит, я посмотрел исходный код в 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
}
0 голосов
/ 13 сентября 2010

Вы можете использовать комбинацию 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 и просто напрямую объединить их.

РЕДАКТИРОВАТЬ

Но в точках ТомасаВы должны использовать комбинаторы Observable вместо Event.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...