debugcsharpMinor
Is this right way to implement Abort, Retry and Ignore activity pattern?
Viewed 0 times
thisignoreimplementwayabortandactivitypatternretryright
Problem
I've a bunch of sequential activities. These are long running. Once an activity is complete, it can't be rolled back. Now something deep down the line fails. Now I've few options
I'm thinking to implement this by defining a custom exception, Action delegate set, coming from exception object. So my question is
Do you see any issue with this pattern? Any better way to do the same?
```
[Serializable]
public class SafetyNetException : Exception
{
public SafetyNetException(SafetyNet callBack) { Net = callBack; }
public SafetyNetException(SafetyNet callBack, string message) : base(message) { Net = callBack; }
public SafetyNetException(SafetyNet callBack, string message, Exception inner) : base(message, inner) { Net = callBack; }
protected SafetyNetException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context)
: base(info, context) { }
public SafetyNet Net { get; private set; }
}
public class SafetyNet
{
public Action Abort;
public Action Retry;
public Action Ignore;
}
[TestClass()]
public class SafetyNetTest
{
[TestMethod()]
public void APIUsageTest()
{
try
{
DoSomething();
}
catch (SafetyNetException targetException)
{
Debug.WriteLine(targetException.Message);
SafetyNet target = targetException.Net;
switch (AskUser())
{
case "r":
target.Retry();
break;
case "a":
target.Abort();
break;
- Report this to end user and ask him to fix something. And then Retry same thing.
- End user has no clue on what to do. He can choose to ignore. (Just retry won't fix things)
- Abort/Pause? Now this is something tricky. This will stop the program. If program is rerun, it should resume from this failed point and not from the starting point.
I'm thinking to implement this by defining a custom exception, Action delegate set, coming from exception object. So my question is
Do you see any issue with this pattern? Any better way to do the same?
```
[Serializable]
public class SafetyNetException : Exception
{
public SafetyNetException(SafetyNet callBack) { Net = callBack; }
public SafetyNetException(SafetyNet callBack, string message) : base(message) { Net = callBack; }
public SafetyNetException(SafetyNet callBack, string message, Exception inner) : base(message, inner) { Net = callBack; }
protected SafetyNetException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context)
: base(info, context) { }
public SafetyNet Net { get; private set; }
}
public class SafetyNet
{
public Action Abort;
public Action Retry;
public Action Ignore;
}
[TestClass()]
public class SafetyNetTest
{
[TestMethod()]
public void APIUsageTest()
{
try
{
DoSomething();
}
catch (SafetyNetException targetException)
{
Debug.WriteLine(targetException.Message);
SafetyNet target = targetException.Net;
switch (AskUser())
{
case "r":
target.Retry();
break;
case "a":
target.Abort();
break;
Solution
You really shouldn't be using exceptions to control the flow of your program's logic. What you're trying to do tends to work much better as a simple state machine that, when it fails, asks whoever is listening what it should do and responds accordingly. Here's a very rough example (which hasn't been tested, is not threadsafe, etc).
In this example, classes would encapsulate sections of retry-able logic by implementing
public interface ISequentialActivity
{
bool Run();
}
public enum UserAction
{
Abort,
Retry,
Ignore
}
public class FailureEventArgs
{
public UserAction Action = UserAction.Abort;
}
public class SequentialActivityMachine
{
private Queue activities = new Queue();
public event Action OnFailed;
protected void PerformOnFailed(FailureEventArgs e)
{
var failed = this.OnFailed;
if (failed != null)
failed(e);
}
public void Add(ISequentialActivity activity) { this.activities.Enqueue(activity); }
public void Run()
{
while (this.activities.Count > 0)
{
var next = activities.Peek();
if (!next.Run())
{
var failureEventArgs = new FailureEventArgs();
PerformOnFailed(failureEventArgs);
if (failureEventArgs.Action == UserAction.Abort)
return;
if (failureEventArgs.Action == UserAction.Retry)
continue;
}
activities.Dequeue();
}
}
}In this example, classes would encapsulate sections of retry-able logic by implementing
ISequentialActivity, returning true from their Run() method if they succeeded and false if they failed in some way. Failure would then be pushed up to the listener through the event for decision making.Code Snippets
public interface ISequentialActivity
{
bool Run();
}
public enum UserAction
{
Abort,
Retry,
Ignore
}
public class FailureEventArgs
{
public UserAction Action = UserAction.Abort;
}
public class SequentialActivityMachine
{
private Queue<ISequentialActivity> activities = new Queue<ISequentialActivity>();
public event Action<FailureEventArgs> OnFailed;
protected void PerformOnFailed(FailureEventArgs e)
{
var failed = this.OnFailed;
if (failed != null)
failed(e);
}
public void Add(ISequentialActivity activity) { this.activities.Enqueue(activity); }
public void Run()
{
while (this.activities.Count > 0)
{
var next = activities.Peek();
if (!next.Run())
{
var failureEventArgs = new FailureEventArgs();
PerformOnFailed(failureEventArgs);
if (failureEventArgs.Action == UserAction.Abort)
return;
if (failureEventArgs.Action == UserAction.Retry)
continue;
}
activities.Dequeue();
}
}
}Context
StackExchange Code Review Q#11032, answer score: 5
Revisions (0)
No revisions yet.