Here's a small sample how easily you can expand functionality with Action and Func delegates without class inheritance or composition by just injection inline code.
What we're trying to do here, is a Retry component, that allows you to repeat a piece of functionality until a time limit or count limit exceeds. For example, using a remote call to a web service can sometimes be a fickle beast, so you might want to try again a few times until the service could come back to life if it's temporarily unavailable.
-==- Usage -==-
List results = null;
// retry maximum of 5 times until deciding that the service is not responding and quit.
Retry.Times(5, () => {
results = ws.GetResults();
});
// retry until 5 minutes have gone or the service responds and returns a value.
Retry.Timespan(TimeSpan.Minutes(5), () => {
results = ws.GetResults();
});
-==- Sample code -==-
public static void Timespan(TimeSpan timespan, Action action, /* optional */ Action onError = null) {
// record start time
DateTime start = DateTime.Now;
// execute action as many times as timelimit allows.
while (DateTime.Now <= start.Add(timespan)) {
if (Execute(action, onError)) return;
}
// Couldn't execute action within the timelimit
// If user provider an error action, execute it.
if (onError != null) onError();
}
public static void Times(int retryCount, Action action, /* optional */ Action onError = null) {
// execute action maximum of [retryCount] times.
for (int i=0; i < retryCount; i++) {
if (Execute(action, onError)) return;
}
// Couldn't execute action in given number of times.
// If user provider an error action, execute it.
if (onError != null) onError();
}
private static bool Execute(Action action, Action onError)
{
// action is required
if (action == null) throw new ArgumentNullException("action", "You didn't provide an action to execute.");
try
{
// execute action
action();
// action didn't throw an exception; success!
return true;
}
catch (Exception ex)
{
// action failed
return false;
}
}
-==- Improvements -==-Of course, as we could be waiting here for a while, we should implement asynchronous calling of the methods so we can execute something else while we wait.
I'm using Task Parallel Library (TPL for short) functionality of .Net 4.0 for this. Of course, the classic Begin/End async pattern would also work here.
-==- Async usage -==-
// retry maximum of 5 times until deciding that the service is not responding and quit.
var task = Retry.Times<List>(5, () => {
return ws.GetResults();
});
... do something else here ...
// blocks until retry is done.
List result = task.Result;
And if you (or more likely the user) need to cancel the request while it's on:
var cancelSource = new CancellationTokenSource();
var task = Retry.TimeSpan<List>(TimeSpan.FromMinutes(15), () => return ws.GetResults(), cancelSource.Token);
... do something here ...
cancelSource.Cancel();
// after canceling the requests, using task.Result will throw System.AggregateException.
Notice that we are using different methodology with sync/async methods. Sync-methods rely on local variables and async-methods on the other hand return a value.
-==- Sample code for async -==-
public static Task<T> Times<T>(int retryCount, Func<T> action, CancellationToken cancellationToken, /* optional */ Action onError = null)
{
Task<T> task = new Task<T>(
() => {
// try [retryCount] times
for (int i=0; i<retryCount; i++) {
// check if user requested cancellation
if (cancellationToken != null)
{
cancellationToken.ThrowIfCancellationRequested();
}
// execute action
try
{
return action();
}
catch (Exception ex)
{
// failed. try again.
}
}
// operation failed each time, execute onerror event if given
if (onError != null)
{
onError();
}
throw new RetryException("Retry method failed.");
},
cancellationToken);
// start executing
task.Start();
return task;
}
You can find more on how TPL can be used with classic async-patterns in here.
-==- Code -==-
As always, the sample code (with tests this time) can be found here.
0 comments:
Post a Comment