F# Async.AwaitEvent и события взаимодействия COM
У меня есть COM-объект, к которому я подключаюсь, и я должен получить событие, которое подтвердит, что соединение установлено. Я пишу код и тестирую его в F# интерактивно, и по какой-то причине он не будет перехватывать события COM, когда я использую Async.RunSynchronously
,
/// This class wraps COM event into F# Async-compatible event
type EikonWatcher(eikon : EikonDesktopDataAPI) =
let changed = new Event<_>()
do eikon.add_OnStatusChanged (fun e -> changed.Trigger true)
member self.StatusChanged = changed.Publish
/// My method
let ``will that connection work?`` () =
let eikon = EikonDesktopDataAPIClass() :> EikonDesktopDataAPI // create COM object
let a = async {
let watcher = EikonWatcher eikon // wrap it
eikon.Initialize() |> ignore // send connection request
let! result = Async.AwaitEvent watcher.StatusChanged // waiting event
printfn "%A" result // printing result
return result
}
// I use either first or second line of code, not both of them
Async.Start (Async.Ignore a) // does not hang, result prints
Async.RunSynchronously (Async.Ignore) a // hangs!!!
/// Running
``will that connection work?`` ()
В то же время код прекрасно работает с RunSynchronously
когда я вставляю его в консольное приложение. Что я должен сделать, чтобы предотвратить это противное поведение?
1 ответ
Код, под которым мы пишем в пределах одного потока (как в STA), чувствует, что он сделан из независимых частей, каждая из которых имеет свою собственную жизнь, но на самом деле это ошибка: все опосредовано в общем цикле событий, который "линеаризует" различные звонки.
Таким образом, все, что мы делаем, если явно не указано иное, по сути является однопоточным, и вы не можете ждать себя, не создавая тупик.
Когда вы указываете Async.Start
он запускает новое, независимое вычисление, которое выполняется само по себе, "поток".
Принимая во внимание, что при вызове выполняется синхронно, он ожидает в том же "потоке".
Теперь, если событие, которое вы ожидаете и которое кажется независимым, на самом деле "линеаризуется" тем же циклом событий, вы фактически ждете себя, отсюда и тупик.
Что-то полезное, если вы хотите ждать "асинхронно" (то есть ждать события, но на самом деле не блокировать и не оставлять возможность для выполнения какой-либо другой задачи), вы можете использовать следующий код в вашем асинхронном блоке:
async {
....
let! token = myAsyncTask |> Async.StartChild
let! result = token
....
}