2015-01-15 7 views
17

Я переписывание всего этот вопроса, потому что я понимаю причину, но все еще нужно решение:замедленного воспламенение срок службы инъекции зависимости сфера

У меня есть повторяющаяся работа в замедленном воспламенении, которая проходит каждую минуту и ​​проверить базу данных, возможно, обновит некоторые материал, затем выходы.

Я вставляю свой dbcontext в класс, содержащий метод задания. Я зарегистрировать DbContext получить впрыскиваются с помощью следующей

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

Тем не менее, кажется, что замедленное воспламенение не создает отдельный пожизненный прицел каждый раз, когда будет выполняться задание, так как конструктор только вызывается один раз, хотя называется метод работа прибудет-х каждую минуту.

Это вызывает проблемы для меня. Если пользователь обновляет некоторые значения в базе данных (dbcontext получает в другое место и используется для обновления значений), контекст, который все еще используется, Hangfire начинает возвращать устаревшие значения, которые уже были изменены.

+2

Вы читаете значение перечисления из окна базы данных или непосредственно из таблицы? Возможно, проблема заключается в материализации EF, а не в самом перечислении. –

+0

Сергей, я уверен, что ты прав. Я добавил более подробную информацию на вопрос – parliament

+0

Если вы считаете, что проблема заключается в материализации EF, посмотрите [этот вопрос] (http://stackoverflow.com/questions/24512780/entity-framework-returns-wrong-data-for -view-with-left-join-statement), возможно, это поможет. –

ответ

18

замедленное воспламенение в настоящее время использует общий экземпляр JobActivator для каждого работника, которые, используя следующий метод для решения зависимости:

public override object ActivateJob(Type jobType) 

планируется добавить JobActivationContext к этому методу для Milestone 2.0.0.

На данный момент нет способа сказать, для какой работы зависимость будет решена. Единственный способ, с помощью которого я могу придумать эту проблему, - использовать тот факт, что задания выполняются последовательно на разных потоках (я не знаю 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)); 
+0

Проблема с этим подходом заключается в том, что он не является потокобезопасным. Если вы выполняете параллельные рабочие задания, возможно, неправильная область может использоваться для неправильной работы. Нам нужно, чтобы JobActivator предоставил метод создания JobActivationScope (реализация DI должна была бы обеспечить тривиальную реализацию вместе с реализацией JobActivator). Затем мы меняем Job.Perform, чтобы создать эту область действия из активатора в операторе using и передать это как параметр для Активировать, а не JobActivator. – HackedByChinese

+0

«Если у вас есть одновременные рабочие задания, возможно, неправильная область может использоваться для неправильной работы». - Но не параллельные задания работают на разных рабочих (потоках)? – Dresel

+0

Прошу прощения. Я упустил атрибут '[ThreadStatic]'. Если мы предоставляем некоторые гарантии относительно внутренних условий потока на разных этапах выполнения, то да, это может работать как временная мера до 2.0.0. Я бы по-прежнему хотел, чтобы модель была больше похожа на то, что я описал, с точки зрения постоянного решения. – HackedByChinese

2

Чтобы обойти эту проблему, я создал одноразовый класс JobContext, который имеет ILifetimeScope, которая будет утилизироваться, когда замедленное воспламенение завершает работу. Реальная работа вызвана отражением.

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); 
3

Edit: С Autofac, .NET 4.5 и замедленное воспламенение> = 1.5.0, используйте Hangfire.Autofac nuget package (github).

Работа с .NET 4.0 (Autofac 3.5.2 и Hangfire 1.1.1), мы создали решение Dresel с Autofac. Только разница в 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); 
     } 
    } 
} 
0

Раствор поддерживается вне коробки, так как hangfire.autofac 2.2.0.

В вашей ситуации, когда ваша зависимость регистрируется на всю жизнь, вы должны использовать non-tagged scopes при настройке hangfire.autofac. От ссылки:

GlobalConfiguration.Configuration.UseAutofacActivator(builder.Build(), false);