Fody Async MethodDecorator to Handle Exceptions
我正在尝试使用 Fody 来package从具有通用异常格式的方法中抛出的所有异常。
所以我添加了所需的接口声明和类实现,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | using System; using System.Diagnostics; using System.Reflection; using System.Threading.Tasks; [module: MethodDecorator] public interface IMethodDecorator { void Init(object instance, MethodBase method, object[] args); void OnEntry(); void OnExit(); void OnException(Exception exception); void OnTaskContinuation(Task t); } [AttributeUsage( AttributeTargets.Module | AttributeTargets.Method | AttributeTargets.Assembly | AttributeTargets.Constructor, AllowMultiple = true)] public class MethodDecorator : Attribute, IMethodDecorator { public virtual void Init(object instance, MethodBase method, object[] args) { } public void OnEntry() { Debug.WriteLine("base on entry"); } public virtual void OnException(Exception exception) { Debug.WriteLine("base on exception"); } public void OnExit() { Debug.WriteLine("base on exit"); } public void OnTaskContinuation(Task t) { Debug.WriteLine("base on continue"); } } |
以及看起来像这样的域实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | using System; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.ExceptionServices; namespace CC.Spikes.AOP.Fody { public class FodyError : MethodDecorator { public string TranslationKey { get; set; } public Type ExceptionType { get; set; } public override void Init(object instance, MethodBase method, object[] args) { SetProperties(method); } private void SetProperties(MethodBase method) { var attribute = method.CustomAttributes.First(n => n.AttributeType.Name == nameof(FodyError)); var translation = attribute .NamedArguments .First(n => n.MemberName == nameof(TranslationKey)) .TypedValue .Value as string; var exceptionType = attribute .NamedArguments .First(n => n.MemberName == nameof(ExceptionType)) .TypedValue .Value as Type; TranslationKey = translation; ExceptionType = exceptionType; } public override void OnException(Exception exception) { Debug.WriteLine("entering fody error exception"); if (exception.GetType() != ExceptionType) { Debug.WriteLine("rethrowing fody error exception"); //rethrow without losing stacktrace ExceptionDispatchInfo.Capture(exception).Throw(); } Debug.WriteLine("creating new fody error exception"); throw new FodyDangerException(TranslationKey, exception); } } public class FodyDangerException : Exception { public string CallState { get; set; } public FodyDangerException(string message, Exception error) : base(message, error) { } } } |
这适用于同步代码。但是对于异步代码,会跳过异常处理程序,即使所有其他 IMethodDecorator 都已执行(如
例如,看下面的测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public class FodyTestStub { [FodyError(ExceptionType = typeof(NullReferenceException), TranslationKey ="EN_WHATEVER")] public async Task ShouldGetErrorAsync() { await Task.Delay(200); throw new NullReferenceException(); } public async Task ShouldGetErrorAsync2() { await Task.Delay(200); throw new NullReferenceException(); } } |
我看到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | // CC.Spikes.AOP.Fody.FodyTestStub [FodyError(ExceptionType = typeof(NullReferenceException), TranslationKey ="EN_WHATEVER"), DebuggerStepThrough, AsyncStateMachine(typeof(FodyTestStub.<ShouldGetErrorAsync>d__3))] public Task ShouldGetErrorAsync() { MethodBase methodFromHandle = MethodBase.GetMethodFromHandle(methodof(FodyTestStub.ShouldGetErrorAsync()).MethodHandle, typeof(FodyTestStub).TypeHandle); FodyError fodyError = (FodyError)Activator.CreateInstance(typeof(FodyError)); object[] args = new object[0]; fodyError.Init(this, methodFromHandle, args); fodyError.OnEntry(); Task task; try { FodyTestStub.<ShouldGetErrorAsync>d__3 <ShouldGetErrorAsync>d__ = new FodyTestStub.<ShouldGetErrorAsync>d__3(); <ShouldGetErrorAsync>d__.<>4__this = this; <ShouldGetErrorAsync>d__.<>t__builder = AsyncTaskMethodBuilder.Create(); <ShouldGetErrorAsync>d__.<>1__state = -1; AsyncTaskMethodBuilder <>t__builder = <ShouldGetErrorAsync>d__.<>t__builder; <>t__builder.Start<FodyTestStub.<ShouldGetErrorAsync>d__3>(ref <ShouldGetErrorAsync>d__); task = <ShouldGetErrorAsync>d__.<>t__builder.Task; fodyError.OnExit(); } catch (Exception exception) { fodyError.OnException(exception); throw; } return task; } |
和
1 2 3 4 5 6 7 8 9 10 11 12 | // CC.Spikes.AOP.Fody.FodyTestStub [DebuggerStepThrough, AsyncStateMachine(typeof(FodyTestStub.<ShouldGetErrorAsync2>d__4))] public Task ShouldGetErrorAsync2() { FodyTestStub.<ShouldGetErrorAsync2>d__4 <ShouldGetErrorAsync2>d__ = new FodyTestStub.<ShouldGetErrorAsync2>d__4(); <ShouldGetErrorAsync2>d__.<>4__this = this; <ShouldGetErrorAsync2>d__.<>t__builder = AsyncTaskMethodBuilder.Create(); <ShouldGetErrorAsync2>d__.<>1__state = -1; AsyncTaskMethodBuilder <>t__builder = <ShouldGetErrorAsync2>d__.<>t__builder; <>t__builder.Start<FodyTestStub.<ShouldGetErrorAsync2>d__4>(ref <ShouldGetErrorAsync2>d__); return <ShouldGetErrorAsync2>d__.<>t__builder.Task; } |
如果我调用
另一方面,
我的问题是,Fody 应该如何生成 IL 以正确注入错误块并使其拦截异步错误?
这是一个包含重现问题的测试的 repo
你需要做的是实现
我认为这仅在您想在进行日志记录或其他操作时重新抛出异常时才有效 - 它不允许您用不同的东西替换异常。你应该测试一下:)
您只是将 try-catch 放置在 'kick-off' 方法的内容周围,这只会保护您,直到它首先需要重新安排('kick-off' 方法将在async 方法首先需要重新调度,因此在 async 方法恢复时不会在堆栈上。
您应该考虑修改在状态机上实现