debugcsharpMinor
Handling COM exceptions / busy codes
Viewed 0 times
exceptionshandlingbusycodescom
Problem
This code writes to Excel using the COM interface. The general issue is that any exception handling has to handle the "Excel is busy" exception. This occurs if information is sent to Excel quicker than it can handle it - eg. latency when a workbook is loaded/created, or the user is playing with the scrollbars (there are good reasons for letting this happen).
This is probably the only example I know of which is simpler and cleaner in VB6 than in C#! In VB6 an ON ERROR would be used. The error handler would then create an error for most cases. But if the error code is a "busy" then it will sleep a short period of time (typically half a second) and then try again with a "RESUME". Don't get me wrong, ON ERROR is generally messier than C#'s try...catch and it is easier to produce awful code; however, this is one example where the VB6 ON ERROR works better. A long sequence of Excel calls can be trapped with one handler. The "RESUME" will then send control back to the line where the 'busy' occurred - this avoids duplicate calls or skipped calls.
The solution I have in C# is to create a while loop with a flag. The flag indicates a repeat of the loop is required due to a 'busy' return from Excel. See the code below.
Is there a simpler, more elegant way of doing this? The main problem is that this requires a method for each type of Excel call. To avoid too many duplicate Excel calls in the busy scenario, the contents of each method is atomic or close to atomic - eg. "write this formatted value"; "apply this formatting to this row". This results in lots of methods. And/or methods with lots of parameters (the example below is a short one with just one format option, but there could be more - colors, decimal points, etc).
```
private void WriteDoubleValue(Excel.Worksheet sh, int x, int y, double lfval, bool bBold)
{
bool bNotSuccess = true;
while (bNotSuccess)
{
try
{
((Excel.Range)sh.Cells[y,x]).set_Value(Missing.Value, lfval);
This is probably the only example I know of which is simpler and cleaner in VB6 than in C#! In VB6 an ON ERROR would be used. The error handler would then create an error for most cases. But if the error code is a "busy" then it will sleep a short period of time (typically half a second) and then try again with a "RESUME". Don't get me wrong, ON ERROR is generally messier than C#'s try...catch and it is easier to produce awful code; however, this is one example where the VB6 ON ERROR works better. A long sequence of Excel calls can be trapped with one handler. The "RESUME" will then send control back to the line where the 'busy' occurred - this avoids duplicate calls or skipped calls.
The solution I have in C# is to create a while loop with a flag. The flag indicates a repeat of the loop is required due to a 'busy' return from Excel. See the code below.
Is there a simpler, more elegant way of doing this? The main problem is that this requires a method for each type of Excel call. To avoid too many duplicate Excel calls in the busy scenario, the contents of each method is atomic or close to atomic - eg. "write this formatted value"; "apply this formatting to this row". This results in lots of methods. And/or methods with lots of parameters (the example below is a short one with just one format option, but there could be more - colors, decimal points, etc).
```
private void WriteDoubleValue(Excel.Worksheet sh, int x, int y, double lfval, bool bBold)
{
bool bNotSuccess = true;
while (bNotSuccess)
{
try
{
((Excel.Range)sh.Cells[y,x]).set_Value(Missing.Value, lfval);
Solution
If I understood you correctly, you have a lot of methods which are identical to the one you've shown except for the parameters they take and the contents of the
To fix this I'd recommend to abstract the "repeat this action as long as Excel is busy" logic into its own method, which takes the action to be repeated as a parameter.
On a style note, I would argue against using Hungarian Notation. It's not really commonly used in .net and basically every style guide written in this century argues against it.
I'd also recommend making the
With these suggestions the code could look like this:
You could then implement
try-block. The rest is repeated code, which is bad.To fix this I'd recommend to abstract the "repeat this action as long as Excel is busy" logic into its own method, which takes the action to be repeated as a parameter.
On a style note, I would argue against using Hungarian Notation. It's not really commonly used in .net and basically every style guide written in this century argues against it.
I'd also recommend making the
bool variable positive (i.e. success instead of notSuccess). This way people don't have to perform double negation in their head when reading things like notSuccess = false (which would be changed to success = true).With these suggestions the code could look like this:
private void TryUntilSuccess(Action action)
{
bool success = false;
while (!success)
{
try
{
action();
success = true;
}
catch (System.Runtime.InteropServices.COMException e)
{
if ((e.ErrorCode & 0xFFFF) == 0xC472)
{ // Excel is busy
Thread.Sleep(500); // Wait, and...
success = false; // ...try again
}
else
{ // Re-throw!
throw e;
}
}
}
}You could then implement
WriteDoubleValue and all the methods like it with a call to TryUntilSuccess like this:TryUntilSuccess( () =>
{
((Excel.Range)sh.Cells[y,x]).set_Value(Missing.Value, lfval);
((Excel.Range)sh.Cells[y, x]).Font.Bold = bBold;
});Code Snippets
private void TryUntilSuccess(Action action)
{
bool success = false;
while (!success)
{
try
{
action();
success = true;
}
catch (System.Runtime.InteropServices.COMException e)
{
if ((e.ErrorCode & 0xFFFF) == 0xC472)
{ // Excel is busy
Thread.Sleep(500); // Wait, and...
success = false; // ...try again
}
else
{ // Re-throw!
throw e;
}
}
}
}TryUntilSuccess( () =>
{
((Excel.Range)sh.Cells[y,x]).set_Value(Missing.Value, lfval);
((Excel.Range)sh.Cells[y, x]).Font.Bold = bBold;
});Context
StackExchange Code Review Q#582, answer score: 7
Revisions (0)
No revisions yet.