25

我正在重写整个问题,因为我意识到了原因,但仍然需要一个解决方案:

我在 Hangfire 中有一个经常性的工作,它每分钟运行一次并检查数据库,可能会更新一些东西,然后退出。

我将我的 dbcontext 注入到包含作业方法的类中。我注册此 dbcontext 以使用以下内容进行注入

builder.RegisterType<ApplicationDbContext>().As<ApplicationDbContext>().InstancePerLifetimeScope();

但是,Hangfire 似乎不会在每次作业运行时创建单独的生命周期范围,因为构造函数只被调用一次,尽管作业方法 get 每分钟调用一次。

这给我带来了问题。如果用户更新数据库中的某些值(dbcontext 被注入到其他地方,并用于更新值),仍在使用的上下文 Hangfire 开始返回已更改的过时值。

4

5 回答 5

20

Hangfire 当前JobActivator为每个 Worker 使用一个共享实例,它们使用以下方法来解决依赖关系:

    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;
    }
}

使用JobFilterwithIServerFilter实现为每个作业(线程)设置此范围:

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));
于 2015-01-19T22:00:08.883 回答
5

我们在 Hangfire.Autofac 中创建了一个新的拉取请求,使用了 Dresel 描述的解决方法。希望它被合并到主分支中:

https://github.com/HangfireIO/Hangfire.Autofac/pull/4

于 2015-04-29T08:29:10.823 回答
4

编辑:使用 Autofac、.NET 4.5 和 Hangfire >= 1.5.0,使用 Hangfire.Autofac nuget 包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);
        }
    }
}
于 2016-07-15T14:26:43.733 回答
2

为了解决这个问题,我创建了一个一次性的 JobContext 类,它有一个 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);
    }
}

为了帮助创建作业,在不使用字符串参数的情况下,还有另一个带有一些扩展的类。

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);
于 2015-07-09T23:58:45.520 回答
1

自hangfire.autofac 2.2.0以来,支持开箱即用的解决方案。

在您的情况下,您的依赖项是在每个生命周期范围内注册的,您应该能够在设置 hangfire.autofac 时使用非标记范围。从链接:

GlobalConfiguration.Configuration.UseAutofacActivator(builder.Build(), false);
于 2017-04-27T20:07:10.807 回答