Constrainted try with retry mechanism for .NET 2.0
Lately I've been working on building a framework of abstractions and features that I feel are rather missing from my daily life. One of these features is a try/retry. In the past I used gotos and a ton of code to do this...but such extra coding was the nature of .NET 1.x. This is the world of .NET 2.0 and therefore I can take it to the next level! My new ExceptionFramework includes a few items to help me with exceptions and I figured I would share the following bit with the world.
using System; public static class ExceptionManager { public static void TryWithRetryConstraints( Int32 retryCount, Int32 sleepTime, ExceptionConstraintCollection constraints, ExceptionTryBlock tryBlock, ExceptionCatchBlock catchBlock) { Int32 n = 0; retry: try { tryBlock.DynamicInvoke( ); } catch (Exception ex) { if (++n < retryCount) { foreach (Object constraint in constraints) { if (constraint is Type) { Boolean isException = false; if (((Type)constraint).Name == "Exception") { isException = true; } Type parent = ((Type)constraint).BaseType; while (!isException && parent.Name != "Object") { if (parent.Name == "Exception") { isException = true; } parent = parent.BaseType; } if (isException) { Exception thrownException = ex; while (thrownException != null) { if (thrownException.GetType( ).ToString( ) == constraint.ToString( )) { Thread.Sleep(sleepTime); goto retry; } thrownException = thrownException.InnerException; } } } else if (constraint is String) { if (ex.Message.Contains((String)constraint)) { Thread.Sleep(sleepTime); goto retry; } } catchBlock.DynamicInvoke(ex); return; } } else { catchBlock.DynamicInvoke(ex); } } } }
Here are some of the support items...
public delegate void ExceptionTryBlock( ); public delegate void ExceptionCatchBlock(Exception ex); public class ExceptionConstraintCollection : Collection<object> { public ExceptionConstraintCollection( ) { // Always declare default constructors! } public ExceptionConstraintCollection(params object[] constraintSet) { for (int n = 0; n < constraintSet.Length; n++) { this.Add(constraintSet[n]); } } }
This version allows me to retry only for certain types of failures. There is a simplified version which doesn't take the constraints, but this is one is a bit more useful for my scenarios (I actually think it's a good idea to always constrain it!) I found that the times I wanted to retry involved cases where I either wanted to retry based on a certain type of exception type or based on some text in the string.
Here's is how I use this version of the try/retry in one of my WPF applications. This is my little way of testing my laptop's RAM speed (as well as the CLR speed). I would grab 1GB of ram, free it, and quickly try to get it again (won't work). The below try/retry helps me get a feel or how fast .NET 2.0 can free up the memory and get it again (sometimes it CAN get the 1GB back).
First, I set up the constraints collection which holds the types and strings to which retries will be constrained to. This could of course be inline with the primary call.
ExceptionConstraintCollection constraints = new ExceptionConstraintCollection( typeof(OutOfMemoryException), "invocation");
Here is the actual call to the try/retry. I tell the try/retry manager how many times to retry (6), how many milliseconds to wait between each try (1000), what constraints to put on the try/retry (the above constraints collection), the logic to run as the try block (a delegate; anonymous in this case), and the code to run as the catch block (also a delegate; also anonymous in this case) and then let it do all the work for me.
ExceptionManager.TryWithRetryConstraints(6, 1000, constraints, delegate { group = new MemoryGroup(); MessageBox.Show("Success"); }, delegate(Exception ex) { ExceptionManager.Report("Unable to allocate memory group.", ex); });
Now there are some obvious issues with this. The first: speed. Well, that's the nature of abstraction. You get ease of use and simplicity to life, but it costs a bit. The second is...this just seem weird, doesn't it? It just seems like there's a whole new bag of issues associated with this. Actually, this doesn't really bother me either. I'm keeping my code small enough so that I don't break the Ritcher anonymous delegate rule (never more than 3 lines of code in an anonymous block including comments) and I'm keeping in my mind that top level exceptions will really have the text of "Exception has been thrown by the target of an invocation." (which is, of course, true, but this means I better not be doing this stuff straight with COM interop!) As long as I keep that last one in mind I should be fine.
In any case, this has really been helping me out and have been proving to be a very powerful solution to the lack of a try/retry in the CLR.