Ожидать любое событие из нескольких событий одновременно в F#
В 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 ответа
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" Томаса Петричека).
Я использовал реализацию метода, который вы используете в своем примере, в беседе о реактивном программировании, которую я проводил в Лондоне (внизу страницы есть ссылка на скачивание). Если вам интересна эта тема, вы также можете найти полезную беседу:-).
Используемая версия берет 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 из моей книги).
Чтобы понять, что происходит, я посмотрел исходный код на 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
}
Вы можете использовать комбинацию 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
из них.