我开始使用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)
{
}
}
我可以在拦截器中看到catch块被命中,但我的测试中的catch块从未被重新抛出。我更困惑,因为这里没有代理或任何东西,只是非常简单的模拟和对象。在我的测试中我也尝试了类似Task.Run(() => interceptor.Intercept(invocation.Object)).Wait();
的东西,但仍然没有变化。测试很愉快,但是nUnit输出确实有异常消息。
我想我搞砸了一些东西,而且我不太了解发生了什么,就像我想的那样。是否有更好的方法来拦截异步方法?关于异常处理,我做错了什么?
答案 0 :(得分:10)
我建议您阅读我的async
/await
intro,如果您还没有这样做的话。您需要非常好地掌握async
方法与其返回的Task
之间的关系,以便拦截它们。
考虑您当前的Intercept
实施。正如svick评论的那样,最好避免使用async void
。一个原因是错误处理异常:async void
方法的任何异常都会直接在当前SynchronizationContext
上引发。
在您的情况下,如果Proceed
方法引发异常(就像您的模拟那样),那么您的async void Intercept
实现将引发异常,该异常将直接发送到SynchronizationContext
(which is a default - or thread pool - SynchronizationContext
since this is a unit test,我在博客上解释)。因此,您将在某个随机线程池线程中看到异常,而不是在单元测试的上下文中。
要解决此问题,您必须重新考虑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;
}
}
}
您可能也希望运行NUnit 2.6.2 or later, which added support for async
unit tests。这样您就可以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
)。尽管有这种限制,但你应该能够使用上述技术做任何你需要的事情。