I am starting to work with ninject interceptors to wrap some of my async code with various behaviors and am having some trouble getting everything working.
Here is an interceptor I am working with:
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;
}
}
This appears to run properly in most normal cases. I am not sure if this will do what I expect. Although it appears to return control flow to the caller asynchronously, I am still a bit worried about the possibility that the proxy is unintentionally blocking a thread or something.
That aside, I cannot get the exception handling working. For this test case:
[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)
{
}
}
I can see in the interceptor that the catch block is hit, but the catch block in my test is never hit from the rethrow. I am more confused because there is no proxy or anything here, just pretty simple mocks and objects. I also tried something like Task.Run(() => interceptor.Intercept(invocation.Object)).Wait();
in my test, and still no change. The test passes happily, but the nUnit output does have the exception message.
I imagine I am messing something up, and I don't quite understand what is going on as much as I think I do. Is there a better way to intercept an async method? What am I doing wrong with regards to exception handling?
I recommend you read my
async
/await
intro, if you haven't already done so. You need a really good grasp of howasync
methods relate to their returnedTask
in order to intercept them.Consider your current
Intercept
implementation. As svick commented, it's best to avoidasync void
. One reason is the error handling is unusual: any exceptions fromasync void
methods are raised on the currentSynchronizationContext
directly.In your case, if the
Proceed
method raises an exception (like your mock will), then yourasync void Intercept
implementation will raise the exception, which will get sent directly to theSynchronizationContext
(which is a default - or thread pool -SynchronizationContext
since this is a unit test, as I explain on my blog). So you will see that exception raised on some random thread pool thread, not in the context of your unit test.To fix this, you must rethink
Intercept
. Regular interception only allows you to intercept the first part of anasync
method; to respond to the result of anasync
method, you'll need to respond when the returnedTask
completes.Here's a simple example that just captures the returned
Task
:You also probably want to be running NUnit 2.6.2 or later, which added support for
async
unit tests. This will enable you toawait
yourMyInterceptor.Result
(which will properly raise the exception in the unit test context).If you want more complex asynchronous interception, you can use
async
- just notasync void
. ;)Unfortunately, interception must Proceed synchronously, so it's not possible to have asynchronous pre-execution (unless you synchronously wait for it to complete, or use
IChangeProxyTarget
). Even with that limitation, though, you should be able to do pretty much anything you need using the techniques above.