2016-01-31 28 views
5

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

答えて

1

'メソッドは、非同期メソッドが最初に再スケジュールする必要があるときに終了し、非同期メソッドが再開したときにスタックには存在しません)。

ステートマシンでIAsyncStateMachine.MoveNext()を実装する方法を変更するのを見てください。特に、非同期メソッドビルダー(AsyncVoidMethodBuilderAsyncTaskMethodBuilderまたはAsyncTaskMethodBuilder<TResult>)上SetException(Exception)への呼び出しを探し、ちょうどそれを渡す前に、例外をラップします。

+0

これは技術的には正しいですが、実装することは困難でした。最後に、ライブラリをhttps://github.com/vescon/MethodBoundaryAspect.Fodyに切り替えました。これは非同期の問題を処理し、私はプロジェクトの作業が簡単で変更が容易であることを発見しました。 – swestner

1

awaitは必ず非同期メソッドが、単純ではありません見えるのですか? :)あなたはその抽象化で漏れを見つけました。最初のawaitが見つかると直ぐにメソッドが戻り、例外ヘルパーは後で例外を傍受する方法がありません。

OnExceptionの両方を実装し、メソッドの戻り値を処理する必要があります。メソッドが復帰し、タスクが完了していないときは、エラーの継続をタスクに巻き戻す必要があります。例外の処理は、例外の処理方法を処理する必要があります。 Fodyの人はそれを考えました。それがOnTaskContinuationのためのものです。 Task.Exceptionをチェックして、タスクに潜んでいる例外があるかどうかを確認し、必要な場合はそれを処理する必要があります。

これは、ロギングや何かをしている間に例外を元に戻したい場合にのみ機能します。違うもので例外を置き換えることはできません。あなたはそれをテストする必要があります:

+0

残念ながらあなたは正しいです、エラーは報告され、再処理されません。 – swestner

関連する問題