Hangfire 依赖注入生命周期范围
Hangfire dependency injection lifetime scope
我正在重写整个问题,因为我知道原因,但仍然需要一个解决方案:
我在 Hangfire 中有一个循环作业,每分钟运行一次并检查数据库,可能会更新一些东西,然后退出。
我将我的 dbcontext 注入到包含作业方法的 class 中。我注册此 dbcontext 以使用以下
进行注入
builder.RegisterType<ApplicationDbContext>().As<ApplicationDbContext>().InstancePerLifetimeScope();
但是,似乎 Hangfire 并没有在每次作业运行时创建一个单独的生命周期范围,因为构造函数只被调用一次,尽管作业方法 get 每分钟调用一次。
这给我带来了麻烦。如果用户更新数据库中的某些值(dbcontext 被注入到其他地方,并用于更新值),仍在使用的上下文 Hangfire 开始返回已经更改的过时值。
Hangfire 目前为每个 Worker 使用 JobActivator
的共享实例,它们使用以下方法来解决依赖关系:
public override object ActivateJob(Type jobType)
计划为 Milestone 2.0.0 添加一个 JobActivationContext 到此方法。
目前,还无法确定解决了哪个作业的依赖关系。我能想到的解决此问题的唯一方法是使用作业在不同线程上 运行 串行这一事实(我不知道 AutoFac,所以我以 Unity 为例)。
您可以创建一个 JobActivator
可以为每个线程存储单独的范围:
public class UnityJobActivator : JobActivator
{
[ThreadStatic]
private static IUnityContainer childContainer;
public UnityJobActivator(IUnityContainer container)
{
// Register dependencies
container.RegisterType<MyService>(new HierarchicalLifetimeManager());
Container = container;
}
public IUnityContainer Container { get; set; }
public override object ActivateJob(Type jobType)
{
return childContainer.Resolve(jobType);
}
public void CreateChildContainer()
{
childContainer = Container.CreateChildContainer();
}
public void DisposeChildContainer()
{
childContainer.Dispose();
childContainer = null;
}
}
使用 JobFilter
和 IServerFilter
实现来为每个作业(线程)设置此范围:
public class ChildContainerPerJobFilterAttribute : JobFilterAttribute, IServerFilter
{
public ChildContainerPerJobFilterAttribute(UnityJobActivator unityJobActivator)
{
UnityJobActivator = unityJobActivator;
}
public UnityJobActivator UnityJobActivator { get; set; }
public void OnPerformed(PerformedContext filterContext)
{
UnityJobActivator.DisposeChildContainer();
}
public void OnPerforming(PerformingContext filterContext)
{
UnityJobActivator.CreateChildContainer();
}
}
最后设置您的 DI:
UnityJobActivator unityJobActivator = new UnityJobActivator(new UnityContainer());
JobActivator.Current = unityJobActivator;
GlobalJobFilters.Filters.Add(new ChildContainerPerJobFilterAttribute(unityJobActivator));
我们在 Hangfire.Autofac 中创建了一个新的拉取请求,解决方法由 Dresel 描述。希望它在主分支中合并:
为了解决这个问题,我创建了一个一次性的 JobContext class,它有一个 ILifetimeScope,当 Hangfire 完成作业时将被释放。真正的工作是由反射调用的。
public class JobContext<T> : IDisposable
{
public ILifetimeScope Scope { get; set; }
public void Execute(string methodName, params object[] args)
{
var instance = Scope.Resolve<T>();
var methodInfo = typeof(T).GetMethod(methodName);
ConvertParameters(methodInfo, args);
methodInfo.Invoke(instance, args);
}
private void ConvertParameters(MethodInfo targetMethod, object[] args)
{
var methodParams = targetMethod.GetParameters();
for (int i = 0; i < methodParams.Length && i < args.Length; i++)
{
if (args[i] == null) continue;
if (!methodParams[i].ParameterType.IsInstanceOfType(args[i]))
{
// try convert
args[i] = args[i].ConvertType(methodParams[i].ParameterType);
}
}
}
void IDisposable.Dispose()
{
if (Scope != null)
Scope.Dispose();
Scope = null;
}
}
有一个 JobActivator 将检查操作并在必要时创建 LifetimeScope。
public class ContainerJobActivator : JobActivator
{
private readonly IContainer _container;
private static readonly string JobContextGenericTypeName = typeof(JobContext<>).ToString();
public ContainerJobActivator(IContainer container)
{
_container = container;
}
public override object ActivateJob(Type type)
{
if (type.IsGenericType && type.GetGenericTypeDefinition().ToString() == JobContextGenericTypeName)
{
var scope = _container.BeginLifetimeScope();
var context = Activator.CreateInstance(type);
var propertyInfo = type.GetProperty("Scope");
propertyInfo.SetValue(context, scope);
return context;
}
return _container.Resolve(type);
}
}
为了帮助创建工作,不使用字符串参数,还有另一个 class 和一些扩展。
public static class JobHelper
{
public static object ConvertType(this object value, Type destinationType)
{
var sourceType = value.GetType();
TypeConverter converter = TypeDescriptor.GetConverter(sourceType);
if (converter.CanConvertTo(destinationType))
{
return converter.ConvertTo(value, destinationType);
}
converter = TypeDescriptor.GetConverter(destinationType);
if (converter.CanConvertFrom(sourceType))
{
return converter.ConvertFrom(value);
}
throw new Exception(string.Format("Cant convert value '{0}' or type {1} to destination type {2}", value, sourceType.Name, destinationType.Name));
}
public static Job CreateJob<T>(Expression<Action<T>> expression, params object[] args)
{
MethodCallExpression outermostExpression = expression.Body as MethodCallExpression;
var methodName = outermostExpression.Method.Name;
return Job.FromExpression<JobContext<T>>(ctx => ctx.Execute(methodName, args));
}
}
所以要排队工作,例如签名如下:
public class ResidentUploadService
{
public void Load(string fileName)
{
//...
}
创建作业的代码如下所示
var localFileName = "Somefile.txt";
var job = ContainerJobActivator
.CreateJob<ResidentUploadService>(service => service.Load(localFileName), localFileName);
var state = new EnqueuedState("queuename");
var client = new BackgroundJobClient();
client.Create(job,state);
编辑: 使用 Autofac、.NET 4.5 和 Hangfire >= 1.5.0,使用 Hangfire.Autofac nuget package (github).
使用 .NET 4.0(Autofac 3.5.2 和 Hangfire 1.1.1),我们使用 Autofac 设置了 Dresel 的解决方案。唯一的区别在于 JobActivator:
using System;
using Autofac;
using Hangfire;
namespace MyApp.DependencyInjection
{
public class ContainerJobActivator : JobActivator
{
[ThreadStatic]
private static ILifetimeScope _jobScope;
private readonly IContainer _container;
public ContainerJobActivator(IContainer container)
{
_container = container;
}
public void BeginJobScope()
{
_jobScope = _container.BeginLifetimeScope();
}
public void DisposeJobScope()
{
_jobScope.Dispose();
_jobScope = null;
}
public override object ActivateJob(Type type)
{
return _jobScope.Resolve(type);
}
}
}
自 hangfire.autofac 2.2.0 以来支持开箱即用的解决方案。
在你的情况下,你的依赖项是在每个生命周期范围内注册的,你应该能够在设置 hangfire.autofac 时使用 non-tagged scopes。来自 link:
GlobalConfiguration.Configuration.UseAutofacActivator(builder.Build(), false);
我正在重写整个问题,因为我知道原因,但仍然需要一个解决方案:
我在 Hangfire 中有一个循环作业,每分钟运行一次并检查数据库,可能会更新一些东西,然后退出。
我将我的 dbcontext 注入到包含作业方法的 class 中。我注册此 dbcontext 以使用以下
进行注入builder.RegisterType<ApplicationDbContext>().As<ApplicationDbContext>().InstancePerLifetimeScope();
但是,似乎 Hangfire 并没有在每次作业运行时创建一个单独的生命周期范围,因为构造函数只被调用一次,尽管作业方法 get 每分钟调用一次。
这给我带来了麻烦。如果用户更新数据库中的某些值(dbcontext 被注入到其他地方,并用于更新值),仍在使用的上下文 Hangfire 开始返回已经更改的过时值。
Hangfire 目前为每个 Worker 使用 JobActivator
的共享实例,它们使用以下方法来解决依赖关系:
public override object ActivateJob(Type jobType)
计划为 Milestone 2.0.0 添加一个 JobActivationContext 到此方法。
目前,还无法确定解决了哪个作业的依赖关系。我能想到的解决此问题的唯一方法是使用作业在不同线程上 运行 串行这一事实(我不知道 AutoFac,所以我以 Unity 为例)。
您可以创建一个 JobActivator
可以为每个线程存储单独的范围:
public class UnityJobActivator : JobActivator
{
[ThreadStatic]
private static IUnityContainer childContainer;
public UnityJobActivator(IUnityContainer container)
{
// Register dependencies
container.RegisterType<MyService>(new HierarchicalLifetimeManager());
Container = container;
}
public IUnityContainer Container { get; set; }
public override object ActivateJob(Type jobType)
{
return childContainer.Resolve(jobType);
}
public void CreateChildContainer()
{
childContainer = Container.CreateChildContainer();
}
public void DisposeChildContainer()
{
childContainer.Dispose();
childContainer = null;
}
}
使用 JobFilter
和 IServerFilter
实现来为每个作业(线程)设置此范围:
public class ChildContainerPerJobFilterAttribute : JobFilterAttribute, IServerFilter
{
public ChildContainerPerJobFilterAttribute(UnityJobActivator unityJobActivator)
{
UnityJobActivator = unityJobActivator;
}
public UnityJobActivator UnityJobActivator { get; set; }
public void OnPerformed(PerformedContext filterContext)
{
UnityJobActivator.DisposeChildContainer();
}
public void OnPerforming(PerformingContext filterContext)
{
UnityJobActivator.CreateChildContainer();
}
}
最后设置您的 DI:
UnityJobActivator unityJobActivator = new UnityJobActivator(new UnityContainer());
JobActivator.Current = unityJobActivator;
GlobalJobFilters.Filters.Add(new ChildContainerPerJobFilterAttribute(unityJobActivator));
我们在 Hangfire.Autofac 中创建了一个新的拉取请求,解决方法由 Dresel 描述。希望它在主分支中合并:
为了解决这个问题,我创建了一个一次性的 JobContext class,它有一个 ILifetimeScope,当 Hangfire 完成作业时将被释放。真正的工作是由反射调用的。
public class JobContext<T> : IDisposable
{
public ILifetimeScope Scope { get; set; }
public void Execute(string methodName, params object[] args)
{
var instance = Scope.Resolve<T>();
var methodInfo = typeof(T).GetMethod(methodName);
ConvertParameters(methodInfo, args);
methodInfo.Invoke(instance, args);
}
private void ConvertParameters(MethodInfo targetMethod, object[] args)
{
var methodParams = targetMethod.GetParameters();
for (int i = 0; i < methodParams.Length && i < args.Length; i++)
{
if (args[i] == null) continue;
if (!methodParams[i].ParameterType.IsInstanceOfType(args[i]))
{
// try convert
args[i] = args[i].ConvertType(methodParams[i].ParameterType);
}
}
}
void IDisposable.Dispose()
{
if (Scope != null)
Scope.Dispose();
Scope = null;
}
}
有一个 JobActivator 将检查操作并在必要时创建 LifetimeScope。
public class ContainerJobActivator : JobActivator
{
private readonly IContainer _container;
private static readonly string JobContextGenericTypeName = typeof(JobContext<>).ToString();
public ContainerJobActivator(IContainer container)
{
_container = container;
}
public override object ActivateJob(Type type)
{
if (type.IsGenericType && type.GetGenericTypeDefinition().ToString() == JobContextGenericTypeName)
{
var scope = _container.BeginLifetimeScope();
var context = Activator.CreateInstance(type);
var propertyInfo = type.GetProperty("Scope");
propertyInfo.SetValue(context, scope);
return context;
}
return _container.Resolve(type);
}
}
为了帮助创建工作,不使用字符串参数,还有另一个 class 和一些扩展。
public static class JobHelper
{
public static object ConvertType(this object value, Type destinationType)
{
var sourceType = value.GetType();
TypeConverter converter = TypeDescriptor.GetConverter(sourceType);
if (converter.CanConvertTo(destinationType))
{
return converter.ConvertTo(value, destinationType);
}
converter = TypeDescriptor.GetConverter(destinationType);
if (converter.CanConvertFrom(sourceType))
{
return converter.ConvertFrom(value);
}
throw new Exception(string.Format("Cant convert value '{0}' or type {1} to destination type {2}", value, sourceType.Name, destinationType.Name));
}
public static Job CreateJob<T>(Expression<Action<T>> expression, params object[] args)
{
MethodCallExpression outermostExpression = expression.Body as MethodCallExpression;
var methodName = outermostExpression.Method.Name;
return Job.FromExpression<JobContext<T>>(ctx => ctx.Execute(methodName, args));
}
}
所以要排队工作,例如签名如下:
public class ResidentUploadService
{
public void Load(string fileName)
{
//...
}
创建作业的代码如下所示
var localFileName = "Somefile.txt";
var job = ContainerJobActivator
.CreateJob<ResidentUploadService>(service => service.Load(localFileName), localFileName);
var state = new EnqueuedState("queuename");
var client = new BackgroundJobClient();
client.Create(job,state);
编辑: 使用 Autofac、.NET 4.5 和 Hangfire >= 1.5.0,使用 Hangfire.Autofac nuget package (github).
使用 .NET 4.0(Autofac 3.5.2 和 Hangfire 1.1.1),我们使用 Autofac 设置了 Dresel 的解决方案。唯一的区别在于 JobActivator:
using System;
using Autofac;
using Hangfire;
namespace MyApp.DependencyInjection
{
public class ContainerJobActivator : JobActivator
{
[ThreadStatic]
private static ILifetimeScope _jobScope;
private readonly IContainer _container;
public ContainerJobActivator(IContainer container)
{
_container = container;
}
public void BeginJobScope()
{
_jobScope = _container.BeginLifetimeScope();
}
public void DisposeJobScope()
{
_jobScope.Dispose();
_jobScope = null;
}
public override object ActivateJob(Type type)
{
return _jobScope.Resolve(type);
}
}
}
自 hangfire.autofac 2.2.0 以来支持开箱即用的解决方案。
在你的情况下,你的依赖项是在每个生命周期范围内注册的,你应该能够在设置 hangfire.autofac 时使用 non-tagged scopes。来自 link:
GlobalConfiguration.Configuration.UseAutofacActivator(builder.Build(), false);