When creating jobs as singletons, there are (at least) two problems:
State can leak between executions in unexpected ways.
Jobs can't consume services that are configured as scoped. This affects database contexts in ASP.Net Core.
Instead, jobs should be registered as scoped or transient with each job execution opening a new scope. Here's my implementation for a job factory:
public class ScopedJobFactory : IJobFactory
{
private readonly IServiceProvider _serviceProvider;
private readonly ConcurrentDictionary<IJob, IServiceScope> _serviceScopes = new ConcurrentDictionary<IJob, IServiceScope>();
public DependencyInjectionJobFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
var jobType = bundle.JobDetail.JobType;
var scope = _serviceProvider.CreateScope();
var job = (IJob)(scope.ServiceProvider.GetService(jobType)
?? ActivatorUtilities.CreateInstance(scope.ServiceProvider, jobType));
_serviceScopes.TryAdd(job, scope);
return job;
}
public void ReturnJob(IJob job)
{
if (job is IDisposable disposable)
{
disposable.Dispose();
}
if (_serviceScopes.TryRemove(job, out var scope))
{
scope.Dispose();
}
}
}
There might be some edge cases with disposing jobs which I haven't tested yet but the general idea should work. As an added bonus, you can schedule jobs without registering them in the service collection, as long as all their dependencies are registered.
When creating jobs as singletons, there are (at least) two problems:
Instead, jobs should be registered as scoped or transient with each job execution opening a new scope. Here's my implementation for a job factory:
There might be some edge cases with disposing jobs which I haven't tested yet but the general idea should work. As an added bonus, you can schedule jobs without registering them in the service collection, as long as all their dependencies are registered.