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