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

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

因此,我可以从Excel工作表中使用它们,这些例程将由button触发的VBAmacros调用。

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

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

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

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

现在,从其他线程调用VBA例程可能会有很多问题。 我必须承认,我不是那种经验丰富的COM,我只看待我的代码和Excel(我使用ATL)之间的黑盒子。

所以,

是否有可能从另一个线程callbackVBA例程?

有没有更好的方法来做我想做的事情?

UPDATE

在权衡了选项并在互联网上阅读了很多东西之后,我会做一些合作的multithreading:在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 

事实上,通过在因特网上进行深度优先search以获得COM加载项,我意识到VBAmacros在与Excel的GUI相同的事件循环中运行,您具有DoEvents设施,并且它不安全(或至less非常棘手)从其他线程callbackVBA程序。 这将需要例如。 诱骗Accesibility设施获取Excel.Application对象的同步句柄,并调用OnTime方法来设置asynchronous事件处理程序。 不值得的麻烦。

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

发表我的评论作为答案…

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

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

另一个select是安排稍后运行,让用户说出什么时候。 (在下面的例子中,我刚刚硬编码了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