不同的策略=新的答案。
我发现了一些你可能会觉得有用的东西:
1.
在 UDF 中,像这样返回 RTD 调用
' excel equivalent: =RTD("GeodesiX.RTD",,"status","Tokyo")
result = excel.WorksheetFunction.rtd( _
"GeodesiX.RTD", _
Nothing, _
"geocode", _
request, _
location)
表现得好像您在单元格中插入了注释函数,而不是 RTD 返回的值。换句话说,“结果”是“RTD 函数调用”类型的对象,而不是 RTD 的答案。相反,这样做:
' excel equivalent: =RTD("GeodesiX.RTD",,"status","Tokyo")
result = excel.WorksheetFunction.rtd( _
"GeodesiX.RTD", _
Nothing, _
"geocode", _
request, _
location).ToDouble ' or ToString or whetever
返回实际值,相当于在单元格中输入“3.1418”。这是一个重要的区别。在第一种情况下,细胞继续参与 RTD 馈送,在第二种情况下,它只是获得一个恒定值。这可能是您的解决方案。
2.
MS VSTO 让编写 Office 插件看起来就像小菜一碟……直到您真正尝试构建一个工业的、可分发的解决方案。为安装程序获得所有权限和权限是一场噩梦,如果您有支持多个 Excel 版本的好主意,情况会变得更糟。我已经使用Addin Express 好几年了。它隐藏了所有这些 MS 讨厌的东西,让我专注于编写我的插件。他们的支持也是一流的,值得一看。 (不,我没有附属或类似的东西)。
3.
请注意,Excel 可以并且会在任何时候调用 Connect / RefreshData / RTD,即使您正在处理某些事情 - 幕后正在进行一些微妙的多任务处理。您需要使用适当的 Synclock 块来装饰您的代码,以保护您的数据结构。
4.
当您收到数据(可能是在单独的线程上异步)时,您绝对必须在最初调用您的线程(通过 Excel)上回调 Excel。如果你不这样做,它会在一段时间内正常工作,然后你会开始出现神秘的、无法解决的崩溃,更糟糕的是,后台出现孤立的 Excel。以下是执行此操作的相关代码示例:
Imports System.Threading
...
Private _Context As SynchronizationContext = Nothing
...
Sub New
_Context = SynchronizationContext.Current
If _Context Is Nothing Then
_Context = New SynchronizationContext ' try valiantly to continue
End If
...
Private Delegate Sub CallBackDelegate(ByVal GeodesicCompleted)
Private Sub GeodesicComplete(ByVal query As Query) _
Handles geodesic.Completed ' Called by asynchronous thread
Dim cbd As New CallBackDelegate(AddressOf GeodesicCompleted)
_Context.Post(Function() cbd.DynamicInvoke(query), Nothing)
End Sub
Private Sub GeodesicCompleted(ByVal query As Query)
SyncLock query
If query.Status = "OK" Then
Select Case query.Type
Case Geodesics.Query.QueryType.Directions
GeodesicCompletedTravel(query)
Case Geodesics.Query.QueryType.Geocode
GeodesicCompletedGeocode(query)
End Select
End If
' If it's not resolved, it stays "queued",
' so as never to enter the queue again in this session
query.Queued = Not query.Resolved
End SyncLock
For Each topic As AddinExpress.RTD.ADXRTDTopic In query.Topics
AddinExpress.RTD.ADXRTDServerModule.CurrentInstance.UpdateTopic(topic)
Next
End Sub
5.
我所做的事情显然类似于您在this addin 中提出的问题。在那里,我从 Google 异步获取地理编码数据,并使用由 UDF 遮蔽的 RTD 提供它。由于调用 GoogleMaps 非常昂贵,我尝试了 101 种方法和几个月的晚上来保持单元格中的价值,就像你正在尝试的那样,但没有成功。我没有计时,但我的直觉是,像“Application.Caller.Value”这样对 Excel 的调用比字典查找慢一个数量级。
最后,我创建了一个缓存组件,它保存并重新加载已经从我在 Workbook OnSave 中动态创建的非常隐藏的电子表格中获得的值。数据存储在 Dictionary(of string, myQuery) 中,其中每个 myQuery 保存所有相关信息。
它运行良好,满足离线工作的要求,甚至对于 20'000 多个公式,它看起来都是瞬时的。
HTH。
编辑:出于好奇,我测试了调用 Excel 比查找字典要贵得多的预感。事实证明,这种预感不仅是正确的,而且令人恐惧。
Public Sub TimeTest()
Dim sw As New Stopwatch
Dim row As Integer
Dim val As Object
Dim sheet As Microsoft.Office.Interop.Excel.Worksheet
Dim dict As New Dictionary(Of Integer, Integer)
Const iterations As Integer = 100000
Const elements As Integer = 10000
For i = 1 To elements + 1
dict.Add(i, i)
Next
sheet = _ExcelWorkbook.ActiveSheet
sw.Reset()
sw.Start()
For i As Integer = 1 To iterations
row = 1 + Rnd() * elements
Next
sw.Stop()
Debug.WriteLine("Empty loop " & (sw.ElapsedMilliseconds * 1000) / iterations & " uS")
sw.Reset()
sw.Start()
For i As Integer = 1 To iterations
row = 1 + Rnd() * elements
val = sheet.Cells(row, 1).value
Next
sw.Stop()
Debug.WriteLine("Get cell value " & (sw.ElapsedMilliseconds * 1000) / iterations & " uS")
sw.Reset()
sw.Start()
For i As Integer = 1 To iterations
row = 1 + Rnd() * elements
val = dict(row)
Next
sw.Stop()
Debug.WriteLine("Get dict value " & (sw.ElapsedMilliseconds * 1000) / iterations & " uS")
End Sub
结果:
Empty loop 0.07 uS
Get cell value 899.77 uS
Get dict value 0.15 uS
在 10'000 个元素的 Dictionary(Of Integer, Integer) 中查找值比从 Excel 中获取单元格值快 超过 11'000 倍。
Q.E.D.