await inside InterceptAsynchronous<TResult>(IInvocation invocation) #191
-
Hello! I have a quick question regarding calling an awaited method inside InterceptAsynchronous. Apologies if this is documented somewhere, I did not see it specifically. The code in question looks like this, where _eventPublisher is an injected Service Bus Publisher:
Running the code above causes the interceptor to be executed twice. If I change:
The interceptor is run only once (although I do not want to introduce blocking calls). Although there do not appear to be any side effects of the method being invoked twice, I am trying to understand exactly why it happens, if it is intended, or if it is dangerous. Has anyone experienced similar behavior? |
Beta Was this translation helpful? Give feedback.
Replies: 6 comments 3 replies
-
Are you using AsyncInterceptorBase? If so, then this is a known issue, which is documented here. If you're not using AsyncInterceptorBase, then no, that's not supposed to happen. Which version of Castle.Core.AsyncInterceptor are you using? There was an issue in an earlier version. |
Beta Was this translation helpful? Give feedback.
-
I will try to put a sample together asap (may be next week). We are using Azure Functions, so I want to replicate exactly what we are doing. Thanks! |
Beta Was this translation helpful? Give feedback.
-
We are not using durable functions. I'll try to get something together over the next day or so. I appreciate your help! |
Beta Was this translation helpful? Give feedback.
-
I cloned the repo and created a really crude test, where the Interceptor awaits another static method. It seems to throw things into a loop which is not what I expected, so maybe I didn't dig deep enough into the tests to understand how they are working. I've attached a zip for you here. |
Beta Was this translation helpful? Give feedback.
-
@azresource I think I've got it. You've highlighted a failing of the documentation. When you need to make an async call before calling
Here's your updated code. Please let me know if this resolves the issue. I'm going to update the documentation with this information. I'll also add some tests. private async Task<TResult> InternalInterceptAsynchronous<TResult>(IInvocation invocation)
{
TResult result;
...
using (var transactionScope = new TransactionScope(TransactionScopeOption.Required, TransactionScopeAsyncFlowOption.Enabled))
{
try
{
var invocationArguments = invocation.Arguments;
AggregateRoot aggregateRoot = invocationArguments
.Where(x => x != null && x.GetType().BaseType == typeof(AggregateRoot)).FirstOrDefault() as AggregateRoot;
CancellationToken cancellationToken = (CancellationToken)invocationArguments
.Where(x => x != null && x.GetType() == typeof(CancellationToken)).FirstOrDefault();
if (_eventPublisher != null && aggregateRoot != null && aggregateRoot.DomainEvents.Any())
{
// Must capture the proceed info prior to awaiting the async call.
IInvocationProceedInfo proceedInfo = invocation.CaptureProceedInfo();
await aggregateRoot.PublishDomainEventsAsync(_eventPublisher, cancellationToken);
// This is the key to allowing the invocation to proceed asynchronously.
proceedInfo.Invoke();
}
else
{
// If no async calls are made, we can just proceed as normal.
invocation.Proceed();
}
var task = (Task<TResult>)invocation.ReturnValue;
result = await task;
transactionScope.Complete();
}
catch (Exception ex)
{
.
.
.
}
finally
{
transactionScope.Dispose();
}
}
} |
Beta Was this translation helpful? Give feedback.
-
That seems to have done the trick! I'll be doing more testing early next week with chained interceptors, but this looks great so far. Thanks! |
Beta Was this translation helpful? Give feedback.
@azresource I think I've got it. You've highlighted a failing of the documentation. When you need to make an async call before calling
Proceed()
, then you must callIInvocationProceedInfo proceedInfo = invocation.CaptureProceedInfo();
prior to the async call. ThenproceedInfo.Invoke();
afterward, instead of callinginvocation.Proceed();
invocation.CaptureProceedInfo();
has an overhead, so if you don't need to make an async call, you can just callinvocation.Proceed();
. You can simplify this by always callingproceedInfo = invocation.CaptureProceedInfo();
thenproceedInfo.Invoke();
Though it will be slower when you don't make an async call before invocation.Here's your updated code. Pleas…