以下是实现Castle Dynamic Proxy库的Intercept
的自定义类型的IInterceptor
方法的代码。此代码段来自基于AOP的日志记录概念验证控制台应用程序,该应用程序已发布here。
public void Intercept(IInvocation invocation)
{
if (Log.IsDebugEnabled) Log.Debug(CreateInvocationLogString("Called", invocation));
try
{
invocation.Proceed();
if (Log.IsDebugEnabled)
if (invocation.Method.ReturnType != typeof(void))
Log.Debug("Returning with: " + invocation.ReturnValue);
}
catch (Exception ex)
{
if (Log.IsErrorEnabled) Log.Error(CreateInvocationLogString("ERROR", invocation), ex);
throw;
}
}
这在常规方法调用中按预期工作,但在尝试使用async
方法时(使用C#5.0中的async/await
关键字)则不行。我相信,我也理解这背后的原因。
为了async/await
工作,编译器将方法的功能主体添加到幕后的状态机中,一旦第一个awaitable
表达式,控件将返回到调用者遇到不能同步完成。
此外,我们可以询问返回类型并确定我们是否正在处理这样的async
方法:
if (invocation.Method.ReturnType == typeof(Task) ||
(invocation.Method.ReturnType.IsGenericType &&
invocation.Method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>)))
Log.Info("Asynchronous method found...");
这仅适用于那些返回async
或Task
而不是Task<>
的{{1}}方法,但我很满意。
void
方法中需要进行哪些更改才能使Intercept
返回到原始调用方而不是原始调用方?
答案 0 :(得分:19)
大概是&#34;问题&#34;是因为它只记录它返回任务 - 你想要在该任务中使用值吗?
假设情况如此,您仍然必须立即将任务返回给调用者 - 而不必等待它完成。如果你打破了这一点,你就会从根本上弄乱事情。
但是,在将任务返回给调用者之前,您应该添加一个继续(通过Task.ContinueWith
),它将在任务完成时记录结果(或失败) 。这仍将提供结果信息,但当然,您可能会在其他日志记录之后将其记录下来。您可能还想要在返回之前立即记录,从而产生如下所示的日志:
Called FooAsync
Returned from FooAsync with a task
Task from FooAsync completed, with return value 5
将结果从任务中取出(如果成功完成)的业务必须通过反射完成,这有点痛苦 - 或者您可以使用动态类型。 (无论哪种方式,它都会受到性能影响。)
答案 1 :(得分:17)
感谢Jon的回答,这就是我最终的结果:
public void Intercept(IInvocation invocation)
{
if (Log.IsDebugEnabled) Log.Debug(CreateInvocationLogString("Called", invocation));
try
{
invocation.Proceed();
if (Log.IsDebugEnabled)
{
var returnType = invocation.Method.ReturnType;
if (returnType != typeof(void))
{
var returnValue = invocation.ReturnValue;
if (returnType == typeof(Task))
{
Log.Debug("Returning with a task.");
}
else if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
{
Log.Debug("Returning with a generic task.");
var task = (Task)returnValue;
task.ContinueWith((antecedent) =>
{
var taskDescriptor = CreateInvocationLogString("Task from", invocation);
var result =
antecedent.GetType()
.GetProperty("Result")
.GetValue(antecedent, null);
Log.Debug(taskDescriptor + " returning with: " + result);
});
}
else
{
Log.Debug("Returning with: " + returnValue);
}
}
}
}
catch (Exception ex)
{
if (Log.IsErrorEnabled) Log.Error(CreateInvocationLogString("ERROR", invocation), ex);
throw;
}
}
答案 2 :(得分:6)
尝试使用通用且干净的解决方案进行澄清:
$('body').on('remove', '.selector', function() {
console.log('Element removed.');
});
方法,将自定义代码添加为延续任务。 我认为最好的解决方案是使用async
关键字绕过编译器类型检查并在运行时解决任务和任务dynamic
之间的区别:
<T>
答案 3 :(得分:4)
我的2美分:
已经正确地确定,对于async
方法,拦截器的目的是通过延续来“增强”通过调用返回的任务。
现在,正是这个任务延续必须返回才能完成拦截器的工作。
因此,基于上述讨论和示例,这对于常规方法以及“原始”async Task
方法非常有效。
public virtual void Intercept(IInvocation invocation)
{
try
{
invocation.Proceed();
var task = invocation.ReturnValue as Task;
if (task != null)
{
invocation.ReturnValue = task.ContinueWith(t => {
if (t.IsFaulted)
OnException(invocation, t.Exception);
});
}
}
catch (Exception ex)
{
OnException(invocation, ex);
}
}
public virtual void OnException(IInvocation invocation, Exception exception)
{
...
}
但在处理async Task<T>
方法时,上述方法会错误地更改拦截返回的任务类型,从Task<T>
到常规Task
请注意,我们正在调用Task.ContinueWith()
而不是Task<TResult>.ContinueWith()
,这是我们要调用的方法。
这将是最终等待此类拦截时产生的异常:
System.InvalidCastException:无法将类型为“System.Threading.Tasks.ContinuationTaskFromTask”的对象强制转换为'System.Threading.Tasks.Task`1
答案 4 :(得分:4)
下面是我正确处理异步方法的异步拦截器适配器实现。
abstract class AsyncInterceptor : IInterceptor
{
class TaskCompletionSourceMethodMarkerAttribute : Attribute
{
}
private static readonly MethodInfo _taskCompletionSourceMethod = typeof(AsyncInterceptor)
.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
.Single(x => x.GetCustomAttributes(typeof(TaskCompletionSourceMethodMarkerAttribute)).Any());
protected virtual Task<Object> InterceptAsync(Object target, MethodBase method, object[] arguments, Func<Task<Object>> proceed)
{
return proceed();
}
protected virtual void Intercept(Object target, MethodBase method, object[] arguments, Action proceed)
{
proceed();
}
[TaskCompletionSourceMethodMarker]
Task<TResult> TaskCompletionSource<TResult>(IInvocation invocation)
{
var tcs = new TaskCompletionSource<TResult>();
var task = InterceptAsync(invocation.InvocationTarget, invocation.Method, invocation.Arguments, () =>
{
var task2 = (Task)invocation.Method.Invoke(invocation.InvocationTarget, invocation.Arguments);
var tcs2 = new TaskCompletionSource<Object>();
task2.ContinueWith(x =>
{
if (x.IsFaulted)
{
tcs2.SetException(x.Exception);
return;
}
dynamic dynamicTask = task2;
Object result = dynamicTask.Result;
tcs2.SetResult(result);
});
return tcs2.Task;
});
task.ContinueWith(x =>
{
if (x.IsFaulted)
{
tcs.SetException(x.Exception);
return;
}
tcs.SetResult((TResult)x.Result);
});
return tcs.Task;
}
void IInterceptor.Intercept(IInvocation invocation)
{
if (!typeof(Task).IsAssignableFrom(invocation.Method.ReturnType))
{
Intercept(invocation.InvocationTarget, invocation.Method, invocation.Arguments, invocation.Proceed);
return;
}
var returnType = invocation.Method.ReturnType.IsGenericType ? invocation.Method.ReturnType.GetGenericArguments()[0] : typeof(object);
var method = _taskCompletionSourceMethod.MakeGenericMethod(returnType);
invocation.ReturnValue = method.Invoke(this, new object[] { invocation });
}
}
和样本用法:
class TestInterceptor : AsyncInterceptor
{
protected override async Task<Object> InterceptAsync(object target, MethodBase method, object[] arguments, Func<Task<object>> proceed)
{
await Task.Delay(5000);
var result = await proceed();
return DateTime.Now.Ticks % 2 == 0 ? 10000 :result;
}
}
答案 5 :(得分:1)
需要拦截返回Task<TResult>
的方法,我创建了Castle.Core
的扩展,简化了流程。
该套餐可在NuGet上下载。
该解决方案主要基于answer的@silas-reinagel,但通过提供实现IAsyncInterceptor的新界面来简化该解决方案。还有进一步的抽象,使拦截类似于实施Interceptor
。
有关详细信息,请参阅项目的readme。
答案 6 :(得分:0)
void IInterceptor.Intercept(IInvocation invocation) { try { invocation.Proceed(); var task = invocation.ReturnValue as Task; if (task != null && task.IsFaulted) throw task.Exception; } catch { throw; } }
答案 7 :(得分:-1)
而不是:
tcs2.SetException(x.Exception);
您应该使用:
x.Exception.Handle(ex => { tcs2.SetException(ex); return true; });
冒出真正的例外......