使Ninject Interceptor可以使用异步方法

cap*_*aig 5 c# ninject async-await ninject-interception

我开始使用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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

在大多数正常情况下,这似乎可以正常运行.我不确定这是否符合我的预期.虽然它似乎异步地将控制流返回给调用者,但我仍然有点担心代理无意中阻塞某个线程或其他东西.

除此之外,我无法让异常处理工作.对于此测试用例:

[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)
    {

    }
}
Run Code Online (Sandbox Code Playgroud)

我可以在拦截器中看到catch块被击中,但我的测试中的catch块从未被重新抛出.我更困惑,因为这里没有代理或任何东西,只是非常简单的模拟和对象.我也尝试了类似于Task.Run(() => interceptor.Intercept(invocation.Object)).Wait();我的测试,但仍然没有变化.测试通过愉快,但nUnit输出确实有异常消息.

我想我搞砸了一些东西,而且我不太清楚我认为发生了什么.是否有更好的方法来拦截异步方法?关于异常处理,我做错了什么?

Ste*_*ary 10

如果你还没有这样做,我建议你阅读我的async/ await介绍.你需要非常好地掌握async方法与他们返回的关系Task,以便拦截它们.

考虑您当前的Intercept实施.正如svick评论的那样,最好避免async void.一个原因是错误处理是不寻常的:async void方法的任何异常都SynchronizationContext直接在当前引发.

在你的情况下,如果Proceed方法引发异常(就像你的模拟那样),那么你的async void Intercept实现将引发异常,它将被直接发送到SynchronizationContext(这是一个默认的 - 或线程池 - SynchronizationContext因为这是一个单元测试,因为我在我的博客上解释.因此,您将在某个随机线程池线程中看到异常,而不是在单元测试的上下文中.

要解决此问题,您必须重新考虑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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您可能还想运行NUnit 2.6.2或更高版本,它增加了对async单元测试的支持.这将使您(awaitMyInterceptor.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);
    }
}
Run Code Online (Sandbox Code Playgroud)

不幸的是,拦截必须同步进行,因此不可能进行异步预执行(除非您同步等待它完成或使用IChangeProxyTarget).即使有这个限制,你应该能够使用上述技术做任何你需要的东西.