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