我们基本上有一个如下所示的类,使用Castle.DynamicProxy进行拦截。
using System;
using System.Collections.Concurrent;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Castle.DynamicProxy;
namespace SaaS.Core.IoC
{
public abstract class AsyncInterceptor : IInterceptor
{
private readonly ILog _logger;
private readonly ConcurrentDictionary<Type, Func<Task, IInvocation, Task>> wrapperCreators =
new ConcurrentDictionary<Type, Func<Task, IInvocation, Task>>();
protected AsyncInterceptor(ILog logger)
{
_logger = logger;
}
void IInterceptor.Intercept(IInvocation invocation)
{
if (!typeof(Task).IsAssignableFrom(invocation.Method.ReturnType))
{
InterceptSync(invocation);
return;
}
try
{
CheckCurrentSyncronizationContext();
var method = invocation.Method;
if ((method != null) && typeof(Task).IsAssignableFrom(method.ReturnType))
{
var taskWrapper = GetWrapperCreator(method.ReturnType);
Task.Factory.StartNew(
async () => { await InterceptAsync(invocation, taskWrapper).ConfigureAwait(true); }
, // this will use current synchronization context
CancellationToken.None,
TaskCreationOptions.AttachedToParent,
TaskScheduler.FromCurrentSynchronizationContext()).Wait();
}
}
catch (Exception ex)
{
//this is not really burring the exception
//excepiton is going back in the invocation.ReturnValue which
//is a Task that failed. with the same excpetion
//as ex.
}
}
....
最初这段代码是:
Task.Run(async () => { await InterceptAsync(invocation, taskWrapper)).Wait()
但是在调用它之后我们失去了HttpContext,所以我们不得不把它切换到:
Task.Factory.StartNew
所以我们可以传入TaskScheduler.FromCurrentSynchronizationContext()
所有这些都很糟糕,因为我们实际上只是将一个线程换成另一个线程。我真的很想改变
的签名void IInterceptor.Intercept(IInvocation invocation)
到
async Task IInterceptor.Intercept(IInvocation invocation)
摆脱Task.Run或Task.Factory,然后制作它:
await InterceptAsync(invocation, taskWrapper);
问题是Castle.DynamicProxy IInterecptor不允许这样做。我真的想在拦截中等待。我可以做.Result但是那时我调用的异步调用的重点是什么?如果没有能够做到等待,我就失去了能够产生这个线程执行的好处。我没有被Castle Windsor用于他们的DynamicProxy,所以我正在寻找另一种方法来做到这一点。我们已经研究过Unity,但我不想替换整个AutoFac实现。
任何帮助都将不胜感激。
答案 0 :(得分:6)
所有这些都很糟糕,因为我们实际上只是将一个线程换成另一个线程。
真。另外因为StartNew
版本实际上并没有等待方法完成;它只会等到第一个await
。但是如果你添加一个Unwrap()
来让它等待完整的方法,那么我强烈怀疑你最终会陷入僵局。
问题是Castle.DynamicProxy IInterecptor不允许这样做。
IInterceptor
确实存在设计限制,必须同步进行。因此,这限制了您的拦截功能:您可以在异步方法之前或之后注入同步代码,并在异步方法之后注入异步代码。在异步方法之前无法注入异步代码。这只是DynamicProxy的一个限制,一个非常难以纠正(例如,打破所有现有用户代码)。
要进行 支持的注入,你必须改变一下你的想法。 async
的有效心理模型之一是从方法返回的Task
表示该方法的执行。因此,要将代码附加到该方法,您可以直接调用该方法,然后使用扩充的方法替换任务返回值。
所以,像这样(对于Task
的返回类型):
protected abstract void PreIntercept(); // must be sync
protected abstract Task PostInterceptAsync(); // may be sync or async
// This method will complete when PostInterceptAsync completes.
private async Task InterceptAsync(Task originalTask)
{
// Asynchronously wait for the original task to complete
await originalTask;
// Asynchronous post-execution
await PostInterceptAsync();
}
public void Intercept(IInvocation invocation)
{
// Run the pre-interception code.
PreIntercept();
// *Start* the intercepted asynchronous method.
invocation.Proceed();
// Replace the return value so that it only completes when the post-interception code is complete.
invocation.ReturnValue = InterceptAsync((Task)invocation.ReturnValue);
}
请注意,PreIntercept
,截获的方法和PostInterceptAsync
都在原始(ASP.NET)上下文中运行。
P.S。快速Google搜索异步DynamicProxy会产生this。不过,我不知道它有多稳定。