Creating Ninject Interceptors Works with Asynchronous Methods

I'm starting to work with ninject interceptors to wrap some of my asynchronous codes with different types of behavior, and I am having trouble getting everything working.

Here is the interceptor I'm 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 works fine in most cases. I am not sure if this will do what I expect. Although it seems that it is returning the control flow asynchronously to the caller, I'm still a little worried about the possibility that the proxy inadvertently blocks the flow or something like that.

Aside, I cannot get exception handling to work. 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 see in the interceptor that the catch block is hit, but the catch block in my test never gets from the retrow. I am more confused because there is no proxy or anything else here, just pretty simple bullying and objects. I also tried something like Task.Run(() => interceptor.Intercept(invocation.Object)).Wait(); in my test and still no changes. The test succeeds, but nUnit has an exception message.

I guess I messed up something, and I don’t quite understand what is happening, as I think. Is there a better way to intercept an asynchronous method? What am I doing wrong with exception handling?

+4
source share
1 answer

I recommend you read my async / await intro if you haven't already. You need to understand very well how the async methods relate to their returned Task in order to intercept them.

Consider the current implementation of Intercept . As svick said, it is better to avoid async void . One reason is that error handling is unusual: any exceptions to the async void methods are created directly on the current SynchronizationContext .

In your case, if the Proceed method throws an exception (for example, your layout), your implementation of async void Intercept will raise an exception that will be sent directly to the SynchronizationContext ( which by default - or the thread pool - SynchronizationContext , since this is a unit test , like I explain on my blog). Thus, you will see this exception raised in some thread in the pool of random threads, and not in the context of your unit test.

To fix this, you need to rethink Intercept . Regular interception only allows you to intercept the first part of the async method; in order to respond to the result of the async method, you will need to respond when the returned Task completed.

Here is a simple example that just grabs the returned 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; } } } 

You might also want to run NUnit 2.6.2 or later, which adds support for async unit tests . This will allow you to await your MyInterceptor.Result (which will throw the exception correctly in the context of the unit test).

If you need a more sophisticated asynchronous capture, you can use async - just not 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); } } 

Unfortunately, the interception must be performed synchronously, so it is not possible to have asynchronous pre-execution (unless you synchronously IChangeProxyTarget for it to complete, or use IChangeProxyTarget ). However, even with this limitation, you should be able to do whatever you need using the methods above.

+8
source

All Articles