Making Ninject Interceptors work with async methods

半腔热情 提交于 2019-12-04 07:21:14

I recommend you read my async/await intro, if you haven't already done so. You need a really good grasp of how async methods relate to their returned Task in order to intercept them.

Consider your current Intercept implementation. As svick commented, it's best to avoid async void. One reason is the error handling is unusual: any exceptions from async void methods are raised on the current SynchronizationContext directly.

In your case, if the Proceed method raises an exception (like your mock will), then your async void Intercept implementation will raise the exception, which will get sent directly to the SynchronizationContext (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 an async method; to respond to the result of an async method, you'll need to respond when the returned Task completes.

Here's a simple example that just captures 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 also probably want to be running NUnit 2.6.2 or later, which added support for async unit tests. This will enable you to await your MyInterceptor.Result (which will properly raise the exception in the unit test context).

If you want more complex asynchronous interception, 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, 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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!