从VBA调用计算密集型例程而不拖延Excel GUI

我有一组捆绑在COM对象中的数字密集型例程(每个需要1分钟才能完成),干净地实现IDispatch

因此,我可以在Excel工作表中使用它们,这些例程将由按钮触发的VBA宏调用。

现在,当调用这些例程之一时,Excel用户界面被冻结,这对于表单的最终用户来说是相当不舒服的。

我想找到任何机制来缓解这个问题。

这可能是例如在COM端启动的另一个线程中启动计算,立即返回,计算结果时生成的线程回调VBA过程。

或者更简单一些,因为我一次只需要执行一次计算。

现在,从其他线程调用VBA例程可能会有很多问题。 我必须承认,我对COM没有那种经验,我只把它当作我的代码和Excel之间的黑盒子(我使用ATL)。

所以,

是否有可能从另一个线程回调VBA例程?

有没有更好的方法去做我想达到的目标?

UPDATE

在权衡了选项并在互联网上阅读了很多东西之后,我将执行合作多线程:在COM对象中,我不会有一个例程,而是有三个:

class CMyObject : ...
{
    ...

    STDMETHOD(ComputationLaunch)(...); // Spawn a thread and return immediately
    STDMETHOD(ComputationQuery)(DOUBLE* progress, BOOL* finished);
    STDMETHOD(ComputationResult)(VARIANT* out);

private:
    bool finished, progress;
    boost::mutex finished_lock, progress_lock;
    ResultObject result; // This will be marshaled to out
                         // when calling ComputationResult
};

而在VBA中:

Private computeActive as Boolean ' Poor man's lock

Public Sub Compute()

    OnError GoTo ErrHandler:

    If computeActive Then Exit Sub
    computeActive = True

    Dim o as MyObject
    call o.ComputationLaunch

    Dim finished as Boolean, progress as Double

    While Not o.ComputationQuery(progress)
        DoEvents
        ' Good place also to update a progress display
    End While

    Dim result as Variant
    result = o.ComputationResult

    ' Do Something with result (eg. display it somewhere)

    computeActive = False
    Exit Sub

ErrHandler:
    computeActive = False
    Call HandleErrors

End Sub

事实上,做互联网的COM加载项上的深度优先搜索,我意识到,VBA宏在同一事件循环与Excel的界面运行,你有DoEvents设施,而且它是不是安全(或至少非常棘手)从其他线程回调VBA程序。 这将需要例如。 欺骗Accesibility设施以获取Excel.Application对象的同步句柄,并调用OnTime方法来设置异步事件处理程序。 不值得麻烦。


如果你想做得好,你需要放弃VBA并编写一个COM加载项。


发表我的评论作为答案...

你可以在你的COM对象中实现一个事件,并在完成时让它回调。 有关如何异步运行COM对象的示例,请参阅http://www.dailydoseofexcel.com/archives/2006/10/09/async-xmlhttp-calls/。


我肮脏的黑客攻击是:创建一个新的Excel实例,在那里运行代码。

另一个选择是安排稍后的运行,让用户说出什么时候。 (在下面的例子中,我刚刚硬编码了5秒。)这仍然会冻结用户界面,但是在预定的时间后。

Sub ScheduleIt()
    Application.OnTime Now + TimeValue("00:00:05"), "DoStuff"
End Sub

Sub DoStuff()
    Dim d As Double
    Dim i As Long
    d = 1.23E+302
    For i = 1 To 10000000#
        ' This loop takes a long time (several seconds).
        d = Sqr(d)
    Next i
    MsgBox "done!"

End Sub
链接地址: http://www.djcxy.com/p/6479.html

上一篇: Calling a computationally intensive routine from VBA without stalling Excel GUI

下一篇: How to get the max no. of columns filled in an XLSX file using POI?