Is this right way to implement Abort, Retry and Ignore activity pattern? 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


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;
                case "i":
                    target.Ignore();
                    break;
            }
        }
    }

    private void DoSomething()
    {
        try
        {
            // try something
        }
        catch (Exception ex)
        {
            SafetyNet net = new SafetyNet();
            net.Abort = () =&gt; DoSomething_Abort();
            net.Retry = () =&gt; DoSomething();
            net.Ignore = () =&gt; DoNextThing();

            throw new SafetyNetException(net, "Ask user", ex);
        }
    }
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).

public interface ISequentialActivity
{
    bool Run();
}

public enum UserAction
{
    Abort,
    Retry, 
    Ignore
}

public class FailureEventArgs
{
    public UserAction Action = UserAction.Abort;
}

public class SequentialActivityMachine
{
    private Queue&lt;ISequentialActivity&gt; activities = new Queue&lt;ISequentialActivity&gt;();
    public event Action&lt;FailureEventArgs&gt; 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 &gt; 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.