c# – Abort \ Retry \ Ignore anywhere in the code to dump the choice on the user

Question:

When operating with the network and IO, limitations are often found that are easy to solve even for a user (albeit an experienced one, yes), but it is difficult to know about them in advance. Busy file, unstable connection – I often miss the redo button, especially when the whole process takes and rolls back.

So far I have thrown on my knee the implementation of a more or less universal solution to this problem:

  public enum ExceptionHandle
  {
    Abort,
    Retry,
    Ignore
  }

  public class ExceptionEventArgs
  {
    public Exception Exception { get; }

    public ExceptionHandle? Handled { get; set; }

    public ExceptionEventArgs(Exception ex)
    {
      this.Exception = ex;
    }
  }

  public static class ExceptionHandler
  {
    public static event EventHandler<ExceptionEventArgs> Handler;

    public static void TryExecute(Action action)
    {
      TryExecute(() => { action(); return true; }, false);
    }

    public static T TryExecute<T>(Func<T> action, T whenIgnored)
    {
      ExceptionHandle? handled = ExceptionHandle.Retry;
      while (handled == ExceptionHandle.Retry)
      {
        try
        {
          return action();
        }
        catch (Exception ex)
        {
          handled = OnHandler(new ExceptionEventArgs(ex));
          if (handled.HasValue)
          {
            switch (handled.Value)
            {
              case ExceptionHandle.Abort:
                throw;
                break;
              case ExceptionHandle.Retry:
                break;
              case ExceptionHandle.Ignore:
                break;
              default:
                throw new ArgumentOutOfRangeException();
            }
          }
          else
          {
            throw;
          }
        }
      }
      return whenIgnored;
    }

    private static ExceptionHandle? OnHandler(ExceptionEventArgs e)
    {
      if (Handler == null || !Handler.GetInvocationList().Any())
      {
        ExceptionDispatchInfo.Capture(e.Exception).Throw();
      }
      else
      {
        Handler.Invoke(null, e);
      }
      return e.Handled;
    }
  }

Thus, any subscriber to ExceptionHandler.Handler can either resolve problems automatically or dump the solution onto the user. Any dangerous code can now be wrapped:

      var tested = ExceptionHandler.TryExecute(() =>
      {
        using (var destination = new MemoryStream())
        {
          using (Stream stream = entry.Open())
            stream.CopyTo(destination);
          return destination.Length == entry.Length;
        }
      }, false);

In general, the current implementation seems to me already bearable and it works. But, I suspect that such solutions are already somewhere, I just could not find them. Can someone advise where to get or at least look at ready-made solutions? Well, if there are jambs in my code, I would also like to help.

UPD: yes, I understand that even so, problematic situations remain – the action can be one-time (close the connection, ruin the sql session, and whatever you want to do). This already remains on the conscience of the person using the code. Although, I would also look at interesting options on this problem, you can limit this fig.

UPD2: I have not yet been able to figure out whether it is possible to wrap one such block in another, but now, as a result, at the abortion of the indoor block, the external one is again sent for processing.

Answer:

"I'm not a doctor, but I can see" (C)

A ready-made solution was found on the Internet and looks pretty good:

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();
        }
    }
}
Scroll to Top