Autofac拦截异步执行顺序
Autofac Interception async execution order
所以我有这个 class:
public class MappingBootstrap : IMappingBootstrap
{
public virtual async Task Map()
{
// Order is very important
await this.mapper1.Map();
await this.mapper2.Map();
await this.mapper3.Map();
await this.mapper4.Map();
}
}
我有 Autofac 拦截器:
public class LoggingInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
var methodReference = Guid.NewGuid();
Console.WriteLine($"Calling {invocation?.Method?.DeclaringType?.Name}.{invocation?.Method?.Name} : {methodReference}");
var startNew = Stopwatch.StartNew();
invocation?.Proceed();
startNew.Stop();
Console.WriteLine($"{methodReference} : Done, time taken: {startNew.ElapsedMilliseconds}ms");
}
}
这会产生输出:
Calling IMapperBootstrap.Map : 54425559-71fe-4f23-ab47-d0f3371ec819
Calling IMapper1.Map : 51babb34-fa83-42ed-84e7-a1e979528116
51babb34-fa83-42ed-84e7-a1e979528116 : Done, time taken: 219ms
54425559-71fe-4f23-ab47-d0f3371ec819 : Done, time taken: 221ms
Calling IMapper2.Map : 41c812a2-d82d-48f6-9b8d-139b52eb28e3
41c812a2-d82d-48f6-9b8d-139b52eb28e3 : Done, time taken: 9ms
Calling IMapper3.Map : c91bed04-8f86-47d3-a35a-417e354c2c5f
c91bed04-8f86-47d3-a35a-417e354c2c5f : Done, time taken: 994ms
Calling IMapper4.Map : 035cad27-1ba8-4bd1-b85f-396f64998d97
035cad27-1ba8-4bd1-b85f-396f64998d97 : Done, time taken: 18ms
如您所见,MappingBoostrap.Map
在第一个 Mapper1.Map
之后完成,而不是我所期望的,即所有功能都已完成。 为什么?
Autofac 配置:
builder.Register(context => new LoggingInterceptor());
builder
.RegisterAssemblyTypes(typeof(Bootstrapper).Assembly)
.Where(x => x.Namespace.Contains("Mapping"))
.AsImplementedInterfaces()
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(LoggingInterceptor));
Why?
当您像这样调用异步方法时:
mapping.Map();
它只启动方法。这是 how asynchronous methods work(正如我在博客中解释的那样)。如果你await
异步方法返回的任务,那么当前方法会暂停,直到异步方法完成:
await mapping.Map();
在 拦截 的情况下,理想的 解决方案是 Proceed
和 Intercept
异步方法:
public async Task InterceptAsync(IInvocation invocation)
{
...
await invocation?.ProceedAsync();
...
}
不幸的是,Autofac 没有对异步方法的内置理解,所以这是不可能的。相反,您必须调用 Proceed
,它只是 启动 异步方法。异步方法returns一个Task
,代表那个方法的执行。要挂钩方法的完成,您应该 将 那个 Task
替换为您自己的方法之一。
对于普通的 Task
-返回方法,您可以使用如下内容:
public void Intercept(IInvocation invocation)
{
var methodReference = Guid.NewGuid();
Console.WriteLine($"Calling {invocation?.Method?.DeclaringType?.Name}.{invocation?.Method?.Name} : {methodReference}");
var startNew = Stopwatch.StartNew();
invocation.Proceed();
invocation.ReturnValue = WatchAsync(methodReference, startNew, (Task)invocation.ReturnValue);
}
private static async Task WatchAsync(Guid methodReference,
Stopwatch stopwatch, Task methodExecution)
{
try
{
await methodExecution.ConfigureAwait(false);
}
finally
{
stopwatch.Stop();
Console.WriteLine($"{methodReference} : Done, time taken: {stopwatch.ElapsedMilliseconds}ms");
}
}
所以我有这个 class:
public class MappingBootstrap : IMappingBootstrap
{
public virtual async Task Map()
{
// Order is very important
await this.mapper1.Map();
await this.mapper2.Map();
await this.mapper3.Map();
await this.mapper4.Map();
}
}
我有 Autofac 拦截器:
public class LoggingInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
var methodReference = Guid.NewGuid();
Console.WriteLine($"Calling {invocation?.Method?.DeclaringType?.Name}.{invocation?.Method?.Name} : {methodReference}");
var startNew = Stopwatch.StartNew();
invocation?.Proceed();
startNew.Stop();
Console.WriteLine($"{methodReference} : Done, time taken: {startNew.ElapsedMilliseconds}ms");
}
}
这会产生输出:
Calling IMapperBootstrap.Map : 54425559-71fe-4f23-ab47-d0f3371ec819
Calling IMapper1.Map : 51babb34-fa83-42ed-84e7-a1e979528116
51babb34-fa83-42ed-84e7-a1e979528116 : Done, time taken: 219ms
54425559-71fe-4f23-ab47-d0f3371ec819 : Done, time taken: 221ms
Calling IMapper2.Map : 41c812a2-d82d-48f6-9b8d-139b52eb28e3
41c812a2-d82d-48f6-9b8d-139b52eb28e3 : Done, time taken: 9ms
Calling IMapper3.Map : c91bed04-8f86-47d3-a35a-417e354c2c5f
c91bed04-8f86-47d3-a35a-417e354c2c5f : Done, time taken: 994ms
Calling IMapper4.Map : 035cad27-1ba8-4bd1-b85f-396f64998d97
035cad27-1ba8-4bd1-b85f-396f64998d97 : Done, time taken: 18ms
如您所见,MappingBoostrap.Map
在第一个 Mapper1.Map
之后完成,而不是我所期望的,即所有功能都已完成。 为什么?
Autofac 配置:
builder.Register(context => new LoggingInterceptor());
builder
.RegisterAssemblyTypes(typeof(Bootstrapper).Assembly)
.Where(x => x.Namespace.Contains("Mapping"))
.AsImplementedInterfaces()
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(LoggingInterceptor));
Why?
当您像这样调用异步方法时:
mapping.Map();
它只启动方法。这是 how asynchronous methods work(正如我在博客中解释的那样)。如果你await
异步方法返回的任务,那么当前方法会暂停,直到异步方法完成:
await mapping.Map();
在 拦截 的情况下,理想的 解决方案是 Proceed
和 Intercept
异步方法:
public async Task InterceptAsync(IInvocation invocation)
{
...
await invocation?.ProceedAsync();
...
}
不幸的是,Autofac 没有对异步方法的内置理解,所以这是不可能的。相反,您必须调用 Proceed
,它只是 启动 异步方法。异步方法returns一个Task
,代表那个方法的执行。要挂钩方法的完成,您应该 将 那个 Task
替换为您自己的方法之一。
对于普通的 Task
-返回方法,您可以使用如下内容:
public void Intercept(IInvocation invocation)
{
var methodReference = Guid.NewGuid();
Console.WriteLine($"Calling {invocation?.Method?.DeclaringType?.Name}.{invocation?.Method?.Name} : {methodReference}");
var startNew = Stopwatch.StartNew();
invocation.Proceed();
invocation.ReturnValue = WatchAsync(methodReference, startNew, (Task)invocation.ReturnValue);
}
private static async Task WatchAsync(Guid methodReference,
Stopwatch stopwatch, Task methodExecution)
{
try
{
await methodExecution.ConfigureAwait(false);
}
finally
{
stopwatch.Stop();
Console.WriteLine($"{methodReference} : Done, time taken: {stopwatch.ElapsedMilliseconds}ms");
}
}