下面是从代码Intercept
上实现自定义类型的方法IInterceptor
所述的城堡动态代理库。 这个片段是从AOP是基于发布日志记录证明的概念控制台应用程序在这里 。
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
方法(使用async/await
从C#5.0关键字)。 而且我相信,我明白这背后的原因也是如此。
对于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<>
而不是void
,但我很好这一点。
什么样的变化已对内进行Intercept
方法,以便awaiter
会回到那里,而不是原来的调用者?
Answer 1:
据推测“问题”是,它只是记录,它的返回一个任务-你想要的任务中的价值 ?
假如是这样的话,你还是要任务返回给调用者,立即 - 无需等待它完成。 如果你打破,你就从根本上搞乱的东西了。
然而,返回任务给调用者之前,你应该添加的延续(通过Task.ContinueWith
),这将记录结果(或失败) 的任务完成时 。 这将仍然给结果的信息,当然你会被一些其他登录后可能作记录。 您可能还需要在返回前立即登录,导致日志是这样的:
Called FooAsync
Returned from FooAsync with a task
Task from FooAsync completed, with return value 5
得到的结果是淘汰任务(如果它成功完成)的业务将不得不与反思完成,这是一个有点疼痛的 - 或者你可以使用动态类型。 (无论哪种方式,这将是一个有点性能损失。)
Answer 2:
由于乔恩的答案,这是我结束了:
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;
}
}
Answer 3:
试图用一个通用的,干净的解决方案,以澄清:
我认为最好的解决方法是使用dynamic
关键字绕过编译器类型检查和解决任务和任务之间的差<T>
在运行时:
public void Intercept(IInvocation invocation)
{
invocation.Proceed();
var method = invocation.MethodInvocationTarget;
var isAsync = method.GetCustomAttribute(typeof(AsyncStateMachineAttribute)) != null;
if (isAsync && typeof(Task).IsAssignableFrom(method.ReturnType))
{
invocation.ReturnValue = InterceptAsync((dynamic)invocation.ReturnValue);
}
}
private static async Task InterceptAsync(Task task)
{
await task.ConfigureAwait(false);
// do the logging here, as continuation work for Task...
}
private static async Task<T> InterceptAsync<T>(Task<T> task)
{
T result = await task.ConfigureAwait(false);
// do the logging here, as continuation work for Task<T>...
return result;
}
Answer 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;
}
}
Answer 5:
我的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
Answer 6:
有一个需要拦截返回方法Task<TResult>
我创建了一个扩展Castle.Core
,简化了流程。
Castle.Core.AsyncInterceptor
该软件包可供下载上的NuGet 。
该解决方案主要是基于此答案由@西拉斯- reinagel ,而是通过提供新的接口来实现简化它IAsyncInterceptor 。 也有进一步的抽象,以使拦截类似于实现Interceptor
。
请参阅自述项目作进一步的细节。
Answer 7:
void IInterceptor.Intercept(IInvocation invocation) { try { invocation.Proceed(); var task = invocation.ReturnValue as Task; if (task != null && task.IsFaulted) throw task.Exception; } catch { throw; } }
Answer 8:
代替:
tcs2.SetException(x.Exception);
您应该使用:
x.Exception.Handle(ex => { tcs2.SetException(ex); return true; });
冒泡真正的例外......
文章来源: Intercept the call to an async method using DynamicProxy