RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 581804
Accepted
Monk
Monk
Asked:2020-10-24 00:21:00 +0000 UTC2020-10-24 00:21:00 +0000 UTC 2020-10-24 00:21:00 +0000 UTC

Abort\Retry\Ignore 在代码中的任何一点都能够将选择转储给用户

  • 772

在处理网络和 IO 时,通常存在即使对于用户(尽管是有经验的用户)也很容易解决的限制,但很难提前了解它们。繁忙的文件,不稳定的连接 - 我经常错过重复按钮,尤其是当整个过程需要并回滚时。

到目前为止,我已经为这个问题实施了一个或多或少通用的解决方案:

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

因此,任何订户ExceptionHandler.Handler都可以自动解决问题,或者将解决方案转交给用户。现在可以包装任何危险代码:

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

总的来说,目前的实施在我看来已经可以忍受并且有效。但是,我怀疑这样的解决方案已经存在于某个地方,我只是找不到它们。有人可以建议从哪里获得或至少查看现成的解决方案吗?好吧,如果我的代码中有门框,我也不会拒绝帮助。

UPD:是的,我知道即使这样也有问题的情况 - 操作可以是一次性的(关闭连接,破坏 sql 会话,无论你想做什么)。它已经留在使用代码的人的良心上。虽然,我也会为这个问题寻找有趣的选项,但你会限制相同的无花果。

UPD2:我还没弄清楚是否有可能将一个这样的块包装到另一个块中,否则现在,结果是,当内部块被中止时,外部块再次进行处理。

c#
  • 3 3 个回答
  • 10 Views

3 个回答

  • Voted
  1. Alexandr Blinov
    2020-10-29T03:37:21Z2020-10-29T03:37:21Z

    “我不是医生,但我能看见” (C)

    在网上找到了完成的方案,看起来还不错:

    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();
            }
        }
    }
    
    • 3
  2. Dmitry D.
    2020-11-02T20:28:06Z2020-11-02T20:28:06Z

    有一种使用PostSharp的方法。
    该解决方案是在示例的基础上开发的:示例 1和示例 2。

    优点:

    • 源代码经过了极小的改动,只有一个属性被添加到方法中。
    • 对方法没有限制。该属性可以应用于静态和实例方法、构造函数和属性,有或没有参数。

    缺陷:

    • 虽然 PostSharp 有免费版本,但普通版本需要付费。也许有具有类似功能的免费类似物......
    • 异常自动处理只对整个方法有效,对部分方法无效。
    • 虽然很小,但是增加了编译时间和代码执行时间。
    • 项目中的附加依赖项。
    • 不可能通过标准方式将委托推送到属性中,因此当前的异常处理程序位于静态属性CurrentExceptionHandler.HandlerFunc中。这是拐杖。

    编码:

    public enum ExceptionHandlerResult
    {
        Abort,
        Retry,
        Ignore
    }
    
    public static class CurrentExceptionHandler
    {
        public static Func<Exception, ExceptionHandlerResult> HandlerFunc { get; set; } = DefaultHandlerFunc;
    
        public static ExceptionHandlerResult DefaultHandlerFunc(Exception e)
        {
            var response = MessageBox.Show(e.ToString(), "Error", MessageBoxButtons.AbortRetryIgnore, MessageBoxIcon.Error);
            switch (response)
            {
                case DialogResult.Abort:
                    return ExceptionHandlerResult.Abort;
                case DialogResult.Retry:
                    return ExceptionHandlerResult.Retry;
                case DialogResult.Ignore:
                    return ExceptionHandlerResult.Ignore;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }
    }
    
    [PSerializable]
    public class HandleExceptionAttribute : MethodInterceptionAspect
    {
        /// <summary>
        /// If the value is negative, ignore the retries count.
        /// </summary>
        public int MaxRetries { get; set; }
    
        public override void OnInvoke(MethodInterceptionArgs args)
        {
            int retriesCount = 0;
    
            while (true)
            {
                try
                {
                    base.OnInvoke(args);
                    return;
                }
                catch (Exception e)
                {
                    Console.WriteLine("Exception during attempt {0} of calling method {1}.{2}: {3}",
                        retriesCount, args.Method.DeclaringType, args.Method.Name, e.Message);
    
                    var handlerFunc = CurrentExceptionHandler.HandlerFunc;
                    if (handlerFunc == null)
                        throw;
    
                    var response = handlerFunc(e);
    
                    switch (response)
                    {
                        case ExceptionHandlerResult.Abort:
                            throw;
                        case ExceptionHandlerResult.Retry:
                            retriesCount++;
                            if (MaxRetries >= 0 && retriesCount > MaxRetries)
                                throw;
                            continue;
                        case ExceptionHandlerResult.Ignore:
                            return;
                    }
                }
            }
        }
    }
    

    使用示例:

    [HandleException(MaxRetries = 10)]
    private static void TestMethod(int n)
    {
        throw new ApplicationException();
    }
    
    TestMethod(1);
    

    要使用自定义异常处理程序,您必须将其设置为静态属性的值CurrentExceptionHandler.HandlerFunc。

    • 2
  3. Best Answer
    VladD
    2020-11-03T19:23:31Z2020-11-03T19:23:31Z

    我不认为这个问题有一个通用的解决方案。由于您编写的代码本质上是“与生活本身一样多样化”的业务逻辑,因此您将无法提前涵盖所有可能的情况。

    副手代码有什么问题:

    1. 执行以阻塞方式同步发生。情况并非总是如此,业务逻辑的“构建块”通常是由异步过程形成的。你没有这个机会。
    2. 第三方对象的事件订阅模型在我看来过于复杂并且违反了线性逻辑。您有一个ExceptionHandler'a 的静态实例,这意味着您必须同时订阅多个处理程序。在这种情况下,需要一种机制来决定给定的处理程序是否对给定的错误负责。你没有这个逻辑,结果会很复杂。此外,一旦您尝试使其变得更复杂,通用对象就应该会遇到多线程访问问题。
    3. 很多时候,简单地重复动作是不够的,因为条件没有改变,这意味着新的动作将以同样的错误结束。需要一些额外的操作:暂停、选择其他初始数据、与用户进行对话等。等等。在您的设计中,所有这些额外的操作都必须打包到事件处理程序中,这不是很可读。

    在我看来,您不应该尝试构建通用逻辑,这是行不通的,因为有很多情况。为各种场合编写小的辅助函数并将它们收集到实用程序类中会更好、更容易和更有效。

    比如这么简单的逻辑

    static async Task<T> TrySeveralTimesWithGrowingTimeout<T>(
        int count, Func<Task<T>> taskCreator, TimeSpan timeoutDiff, Action<string> failureLogger)
    {
        TimeSpan timeout = TimeSpan.Zero;
        for (int i = 0; i < count; i++) // count раз:
        {
            timeout += timeoutDiff; // нарастим таймаут
            try
            {                               // пытаемся выполнить Task, если не будет
                return await taskCreator(); // исключения, возвращаем результат
            }
            catch (Exception ex) when (i < count - 1) // ловим исключение всегда, кроме
            {                                         // последней итерации
                failureLogger($"Operation failed #{i}, retrying after {timeout}. " +
                              $"Exception was {ex.Message}"); // залогировали
            }
            await Task.Delay(timeout);     // выдерживаем паузу до следующего запуска
        }
        // сюда мы не попадём, т. к. если последняя итерация провалилась,
        // то было выброшено исключение
        throw new Exception("cannot happen");
    }
    

    很难用ExceptionHandler'a.

    使用这样的测试函数:

    async Task<string> GetFileContent(string path)
    {
        using (var sr = new StreamReader(path))
            return await sr.ReadToEndAsync();
    }
    
    // ...
    
    try
    {
        var text = await TrySeveralTimesWithGrowingTimeout(
                count: 3,
                taskCreator: () => GetFileContent(path),
                timeoutDiff: TimeSpan.FromSeconds(10),
                failureLogger: Console.Error.WriteLine);
        // тут ещё километр логики
        Console.WriteLine(text);
    }
    catch (IOException ex)
    {
        Console.Error.WriteLine($"Failed: {ex.Message}");
    }
    

    但这不是一个通用的功能,它只是一个可以很容易地手工编码的例子。

    • 2

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    如何停止编写糟糕的代码?

    • 3 个回答
  • Marko Smith

    onCreateView 方法重构

    • 1 个回答
  • Marko Smith

    通用还是非通用

    • 2 个回答
  • Marko Smith

    如何访问 jQuery 中的列

    • 1 个回答
  • Marko Smith

    *.tga 文件的组重命名(3620 个)

    • 1 个回答
  • Marko Smith

    内存分配列表C#

    • 1 个回答
  • Marko Smith

    常规赛适度贪婪

    • 1 个回答
  • Marko Smith

    如何制作自己的自动完成/自动更正?

    • 1 个回答
  • Marko Smith

    选择斐波那契数列

    • 2 个回答
  • Marko Smith

    所有 API 版本中的通用权限代码

    • 2 个回答
  • Martin Hope
    jfs *(星号)和 ** 双星号在 Python 中是什么意思? 2020-11-23 05:07:40 +0000 UTC
  • Martin Hope
    hwak 哪个孩子调用了父母的静态方法?还是不可能完成的任务? 2020-11-18 16:30:55 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    user207618 Codegolf——组合选择算法的实现 2020-10-23 18:46:29 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    Arch ArrayList 与 LinkedList 的区别? 2020-09-20 02:42:49 +0000 UTC
  • Martin Hope
    iluxa1810 哪个更正确使用:if () 或 try-catch? 2020-08-23 18:56:13 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5