F#Async.AwaitEvent和COM互操作事件

F#Async.AwaitEvent和COM互操作事件,f#,event-handling,com-interop,F#,Event Handling,Com Interop,我有一个COM对象,我连接到它,我应该收到一个事件,它将确认连接已建立。我在F#interactive中编写代码并进行测试,由于某种原因,当我使用Async.RunSynchronously时,它不会捕获COM事件 /// This class wraps COM event into F# Async-compatible event type EikonWatcher(eikon : EikonDesktopDataAPI) = let changed = new Event<

我有一个COM对象,我连接到它,我应该收到一个事件,它将确认连接已建立。我在F#interactive中编写代码并进行测试,由于某种原因,当我使用
Async.RunSynchronously
时,它不会捕获COM事件

/// 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?`` ()
///此类将COM事件包装为F#异步兼容事件
类型EikonWatcher(eikon:EikonDesktopDataAPI)=
let changed=新事件()
do eikon.add_OnStatusChanged(乐趣e->changed.Trigger true)
成员self.StatusChanged=已更改。发布
///我的方法
让``那个连接能用吗?`(`)=
让eikon=EikonDesktopDataAPIClass():>EikonDesktopDataAPI//创建COM对象
设a=async{
让watcher=EikonWatcher-eikon//包装它
eikon.Initialize()|>忽略//发送连接请求
let!result=Async.AwaitEvent watcher.StatusChanged//waiting event
printfn“%A”结果//打印结果
返回结果
}
//我使用第一行或第二行代码,而不是两行
Async.Start(Async.Ignore a)//不挂起,结果打印
Async.RunSynchronously(Async.Ignore)a//挂起!!!
///运行
``那个连接有用吗?`()
同时,当我将代码插入控制台应用程序时,它可以与
同步运行。

我应该怎么做才能防止这种恶劣的行为呢?

我们在一个线程中编写的代码(如在STA中)感觉它是由独立的片段组成的,每个片段都有自己的生命,但这实际上是一种谬论:所有的东西都在一个共同的事件循环下进行调解,它“线性化”各种各样的电话

所以我们所做的一切,除非有明确的说明,基本上都是单线程的,你不能等待自己而不造成死锁

当您指定
Async.Start
时,它确实会启动一个新的独立计算,该计算本身运行,即“线程”

而当您以同步方式调用runsynchronous时,它将在同一“线程”上等待

现在,如果您正在等待的事件(感觉像是一个独立的事件)实际上被相同的事件循环“线性化”,那么您实际上是在等待自己,因此出现了死锁


如果您希望“异步”等待(也称为等待事件,但不实际阻止并为任何其他任务留下执行工作的机会),则可以在异步块中使用以下代码:

async {
         ....
         let! token = myAsyncTask |> Async.StartChild
         let! result = token
         ....  
}

线程不是COM组件的次要细节。F#交互式控制台无疑在其入口点上使用[StatThread],承诺为单线程COM对象提供支持。控制台应用程序没有,默认情况下为[MTAThread]。这迫使COM创建另一个线程,为COM组件提供一个好客的家。这样的线程所做的最重要的事情是泵送一个消息循环。不确定RunSynchronously()做什么,显然没有泵送消息循环。所以它只是死锁,因为COM组件不能触发它的事件。好吧,一般来说,我相信情况就是这样。但是,我仍然不知道如何修复它(这必须在交互式F#控制台中运行吗?你可以启动一个线程来运行这段代码,让COM再次解决你的问题。@HansPassant在FSI中运行东西几乎是F#开发的一个标准部分。不能做到这一点将是一个相当大的障碍。runsynchronously不是会等待,但既然等待,那么消息泵不处理(如果[StatThread]=>shared pump),COM将永远没有机会发送消息?+1感谢您的建议,不幸的是,使用子任务或异步并不能解决问题,程序行为没有改变有多奇怪。如果我尝试使用excel/COM,我应该重新生成