QueueAsMacro would not work - ever again!
See original GitHub issueThis is a little tricky explain and reproduce. But in some specific scenario we can ended up with QueueAsMacro
not running ever again.
This is something pretty critical issue…
Description
If you run QueueAsMacro
while the Excel dose not have any spread sheet open, it will be queued and run - we can argue if it should, as there is no file open, but it will.
Next, we open some file. Obviously the code will run.
Now we close the file (instance of the Excel is still open), and try to run QueueAsMacro
.
This will fail.
But unfortunately any other invocation of QueueAsMacro
(even from the opened spread sheet) will fail - we will never “dequeue” work.
Simple Repro-steps
- Open excel without any file
- Run something using
QueueAsMacro
. - Open some file (could be ‘new empty file’) in Excel and try to use
QueueAsMacro
- this should work - Close the file, leaving the Excel open
- Run something using
QueueAsMacro
- now this will fail (because this line) - Open some file (could be ‘new empty file’)
- Try to use
QueueAsMacro
- this will fail - (because this line)
Explanation
For some reason, when you run ExcelDnaUtil.Application
when there is no file for the first time (just after opening Excel) - will return proper object. This is whats allow us to run code and use QueueAsMacro
even if no file is open.
But when we run this second time (after closing some file) this will throw exception, because check in CheckExcelApiAvailable
return XlReturnFailed
.
And I’m talking about running this from Main thread, because if we run this from some other thread, ExcelDnaUtil.Application
will return null
if there is no file open.
So, if we are in 5th step (from repro-steps), we will enqueue task and set the flag _syncPosted
to true
.
Not this will be “picked” to run by ProcessRunSyncMacroMessage()
and we will try to COMRunMacro
.
But in this case we will get exception when we try to get Application
.
This exception is not caught anywhere, so we will not “schedule” this to run later (using timer). We will just exit - leaving the flag _syncPosted
set, and not running PostRunSyncMacro
ever again.
Any other attempts to use QueueAsMacro
will just add task to queue and wait (indefinitely) to be executed.
Possible solutions
I’m not that comfortable with the ExcelDNA code to create PR. (and I don’t want install Framework2.0 to be able to compile the project 😝 ) So this is my potential ideas how we can solve this problem.
- use
try/cache
when we try to get Application (here) or just add additionalcache
to the “main try”. The downside of this, will be different behavior for the first “empty excel” and the rest, because at the beginning we will not get exception. - Add additional check
if (IsInFormulaEditMode() || IsNoFileOpen()) return false;
. We will have the same behavior in both cases - we will not run any macros until we open some file. Downside: we could ended with quite big queue and we will run (e.g. 50 tasks) all of them second after we open some file 🤔 - Change the API and return
bool
fromQueueAsMacro
- if true, we enqueue task, if false, we was unable to do so, because no file was opened. So before we add something to the queue we would have to check if some file has been opened. - change where we clear the
_syncPosted
. Because right now we will clear only when we “clear”_isRunningMacros
(which is redundant) - we should clear it just after we set runningMacros. And in addition use some new flag to indicate “something went wrong, try to run_syncWindow.PostRunSyncMacro()
again”.
Issue Analytics
- State:
- Created 5 years ago
- Comments:7 (5 by maintainers)
Top GitHub Comments
@Mad-Lynx Thank you for reporting this - I’ll have a look. Getting the Application object from Excel before the first workbook is opened is a huge problem, and the reason for a lot of the complicated code here. Excel has not internally created the COM objects and we need to get Excel to do so. Later, after all files are closed, I don’t think there is really a problem. This situation is just not handled well by Excel-DNA. I expect it to be easy to fix it I can recreate according to your steps. I’ll have a look over the weekend.
@Mad-Lynx That makes sense.
You should pretty much never call
Marshal.ReleaseComObject
anywhere. The .NET GC properly keeps track of COM object references for you. Sometimes it makes sense to do a manualGC.Collect()
to ensure COM objects that you are done with will be cleaned up and hence released by the GC immediately. Only case I can think of for usingMarshal.ReleaseComObject
is if you are using low-level COM calls where there is no RCW (Runtime-Callable Wrapper) and you are dealing with theIUnknown
pointer directly through P/Invoke.I’ve tried to write about the
Marshal.ReleaseComObject
anti-pattern on StackOverflow: https://stackoverflow.com/questions/37904483/as-of-today-what-is-the-right-way-to-work-with-com-objects