Search code examples
.netexcelvisual-studioexcel-dna

How to not hang Excel?


I have this problem where my volatile, macro type UDF that sets the value of a couple of cells next to the calling cell works perfectly unless used twice (or more) in the sheet: the Excel status bar starts flashing "Calculating...".

I't not a real hang but an heavy load of recalculations: the counter in the example function are drawn without the choppiness of actual hangs.

Also I am not sure about using a volatile UDF, as I need to have automatic recalculation, but only on input changes, rather than constant polling.

I did produce an example module with three functions:

  • VolatileNyanCat - Similar to my UDF: a single SetValue call. Excel hangs if used two times.
  • NonVolatileOneNyanCat - As above, but not volatile. Doesn't hang, but I need auto-recalculation.
  • HangNyanCat - It calls SetValue two times on two different ranges. Excel hangs.

I think that this has to do with how ExcelAsyncUtil.QueueAsMacro works and with thread safety, but I'm baffled here.

Imports ExcelDna.Integration
Imports ExcelDna.Integration.XlCall

Public Module Example

    <ExcelFunction(IsMacroType:=True, IsVolatile:=True)>
    Public Function VolatileNyanCat() As String

        Dim caller = CType(XlCall.Excel(XlCall.xlfCaller), ExcelReference)
        Dim NyanCat(,) As String = {{"Nyan", "Cat"}}

        Dim nc As New ExcelReference(caller.RowFirst + 1, caller.RowLast + 1,
                                     caller.ColumnFirst, caller.ColumnLast + 1,
                                     caller.SheetId)

        Static nyy As Integer = 1

        ExcelAsyncUtil.QueueAsMacro(Sub()
                                        nyy += 1
                                        nc.SetValue(NyanCat)
                                    End Sub)
        Return "NYA! x " + CStr(nyy)
    End Function

    <ExcelFunction(IsMacroType:=True)>
    Public Function NonVolatileOneNyanCat() As String

        Dim caller = CType(XlCall.Excel(XlCall.xlfCaller), ExcelReference)
        Dim NyanCat(,) As String = {{"Nyan", "Cat"}}

        Dim nc As New ExcelReference(caller.RowFirst + 1, caller.RowLast + 1,
                                     caller.ColumnFirst, caller.ColumnLast + 1,
                                     caller.SheetId)

        Static nyy As Integer = 1

        ExcelAsyncUtil.QueueAsMacro(Sub()
                                        nyy += 1
                                        nc.SetValue(NyanCat)
                                    End Sub)
        Return "NYA! x " + CStr(nyy)
    End Function

    <ExcelFunction(IsMacroType:=True, IsVolatile:=True)>
    Public Function HangNyanCat() As String

        Dim caller = CType(XlCall.Excel(XlCall.xlfCaller), ExcelReference)
        Dim nyan(,) As String = {{"Nyan"}} : Dim cat(,) As String = {{"Cat"}}

        Dim n As New ExcelReference(caller.RowFirst + 1, caller.RowLast + 1,
                                    caller.ColumnFirst, caller.ColumnLast,
                                    caller.SheetId)

        Dim c As New ExcelReference(caller.RowFirst + 1, caller.RowLast + 1,
                                    caller.ColumnFirst + 1, caller.ColumnLast + 1,
                                    caller.SheetId)

        Static nyy As Integer = 1

        ExcelAsyncUtil.QueueAsMacro(Sub()
                                        n.SetValue(nyan)
                                        c.SetValue(cat)
                                        nyy += 1
                                    End Sub)
        Return "NYA! x " + CStr(nyy)
    End Function
End Module

Solution

  • ExcelAsyncUtil.QueueAsMacro runs the code as soon as possible - in this case when the calculation completes. But the macro you run sets some values on the sheet, so the volatile functions will calculate again. Which queues up a new macro to set some cells... Etc. So everything seems to work as expected.

    Setting other cells from inside a UDF is not recommended and blocked by Excel - which is why you have to go through the weird macro route. If you can restructure your functions to be without such side effects, Excel will become much happier.