Fodyを使用して、共通の例外フォーマットを持つメソッドからスローされたすべての例外をラップしようとしています。Fody非同期MethodDecoratorが例外を処理する
だから私はこのようになります必要なインタフェースの宣言とクラスの実装追加しました:
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");
}
}
そしてこれは、同期コードのために正常に動作します。この
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(OnExit
、およびOnTaskContinuation
など)が実行されていても、非同期コードの場合、例外ハンドラはスキップされます。
// 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;
}
そしてShouldGetErrorAsync2
を生成します:
// 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;
}
場合、私はShouldGetErrorAsync
には、次のILコードを生成していることがわかり
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();
}
}
:次のテストクラスを見て例えば
、私はShouldGetErrorAsync
と呼ばれ、Fodyは傍受されているメソッド本体をtryキャッチでラップします。しかし、メソッドが非同期の場合、fodyError.OnTaskContinuation(task)
とfodyError.OnExit()
がまだ呼び出されていてもcatchステートメントにヒットしません。
一方、ILにエラー処理ブロックがなくても、ShouldGetErrorAsync
はエラーをうまく処理します。
私の質問は、エラーブロックを適切に挿入して非同期エラーが傍受されるようにILを生成する方法です。あなたはこれだけが唯一、それが最初のスケジュールを変更する必要があるポイント(「キックオフにあなたを守ってくれる、「キック・オフ」方式のコンテンツの周りのtry-catchを置いている
Here is a repo with tests that reproduces the issue
これは技術的には正しいですが、実装することは困難でした。最後に、ライブラリをhttps://github.com/vescon/MethodBoundaryAspect.Fodyに切り替えました。これは非同期の問題を処理し、私はプロジェクトの作業が簡単で変更が容易であることを発見しました。 – swestner