5

ninjectインターセプターを使用して、非同期コードの一部をさまざまな動作でラップし始めていますが、すべてを機能させるのに問題があります。

これが私が使用しているインターセプターです:

public class MyInterceptor : IInterceptor
{
    public async void Intercept(IInvocation invocation)
    {
        try
        {
            invocation.Proceed();
            //check that method indeed returns Task
            await (Task) invocation.ReturnValue;
            RecordSuccess();
        }
        catch (Exception)
        {
            RecordError();
            invocation.ReturnValue = _defaultValue;
            throw;
        }
    }

これは、ほとんどの通常の場合に正しく実行されるように見えます。これが私が期待することをするかどうかはわかりません。制御フローを非同期的に呼び出し元に返すように見えますが、プロキシが意図せずにスレッドなどをブロックしている可能性については、まだ少し心配しています。

それはさておき、例外処理を機能させることはできません。このテストケースの場合:

[Test]
public void ExceptionThrown()
{
    try
    {
        var interceptor = new MyInterceptor(DefaultValue);
        var invocation = new Mock<IInvocation>();
        invocation.Setup(x => x.Proceed()).Throws<InvalidOperationException>();
        interceptor.Intercept(invocation.Object);
    }
    catch (Exception e)
    {

    }
}

インターセプターでキャッチブロックがヒットしていることがわかりますが、テストのキャッチブロックが再スローからヒットすることはありません。ここにはプロキシなどがなく、非常に単純なモックとオブジェクトがあるため、私はもっと混乱しています。私も自分のテストのようなことを試しTask.Run(() => interceptor.Intercept(invocation.Object)).Wait();ましたが、それでも変化はありません。テストは問題なく合格しますが、nUnit出力には例外メッセージがあります。

私は何かを台無しにしていると想像します、そして私は私が思っているほど何が起こっているのかよく理解していません。非同期メソッドをインターセプトするためのより良い方法はありますか?例外処理に関して何が間違っていますか?

4

1 に答える 1

10

まだ読んでいない場合は、私のasync/awaitイントロを読むことをお勧めします。メソッドをインターセプトするには、asyncメソッドが返されるものとどのように関連しているかをよく理解する必要があります。Task

Intercept現在の実装を検討してください。svickがコメントしたように、避けるのが最善async voidです。1つの理由は、エラー処理が異常であるということです。async voidメソッドからの例外は、現在の場所でSynchronizationContext直接発生します。

あなたの場合、Proceedメソッドが例外を発生させると(モックのように)、実装は例外を発生させます。これは、ユニットテストであるため(デフォルトまたはスレッドプール)async void Interceptに直接送信されます。ブログで説明します)。したがって、単体テストのコンテキストではなく、ランダムなスレッドプールスレッドで例外が発生することがわかります。SynchronizationContextSynchronizationContext

これを修正するには、を再考する必要がありますIntercept。通常のインターセプトでは、メソッドの最初の部分のみをインターセプトできます。asyncメソッドの結果async応答するには、返されたものが完了したときに応答する必要がありますTask

返されたものをキャプチャする簡単な例を次に示しTaskます。

public class MyInterceptor : IInterceptor
{
    public Task Result { get; private set; }

    public void Intercept(IInvocation invocation)
    {
        try
        {
            invocation.Proceed();
            Result = (Task)invocation.ReturnValue;
        }
        catch (Exception ex)
        {
            var tcs = new TaskCompletionSource<object>();
            tcs.SetException(ex);
            Result = tcs.Task;
            throw;
        }
    }
}

また、単体テストのサポートが追加されたNUnit2.6.2以降をasync実行することもお勧めします。これにより、次のことが可能になりawaitますMyInterceptor.Result(単体テストのコンテキストで例外が適切に発生します)。

より複雑な非同期インターセプトが必要な場合は、を使用できますが、ではasyncありませんasync void。;)

// Assumes the method returns a plain Task
public class MyInterceptor : IInterceptor
{
    private static async Task InterceptAsync(Task originalTask)
    {
        // Await for the original task to complete
        await originalTask;

        // asynchronous post-execution
        await Task.Delay(100);
    }

    public void Intercept(IInvocation invocation)
    {
        // synchronous pre-execution can go here
        invocation.Proceed();
        invocation.ReturnValue = InterceptAsync((Task)invocation.ReturnValue);
    }
}

残念ながら、インターセプトは同期的に続行する必要があるため、非同期の事前実行を行うことはできません(同期的に完了するのを待つか、を使用しない限りIChangeProxyTarget)。ただし、その制限があっても、上記の手法を使用して、必要なほとんどすべてのことを実行できるはずです。

于 2012-11-30T04:36:57.663 に答える