Search code examples
c#cronquartz.netcron-task

Quartz.net scheduler gets garbage collected


Currently have 12 jobs running using quartz.net.

The first 10 jobs need to run every 30 minutes. The second 2 jobs need to run every 3 minutes.

The problem is after the 2 jobs that run every 3minutes are done. The program stops working. It doesn't crash the console window is still responding but the app doesn't fire jobs anymore.

I assume that this is because the scheduler got garbage collected. Does somebody have a solution to this?

StartAsync:

public async Task StartAsync(CancellationToken cancellationToken)
{
    var scheduler = await GetScheduler();
    await scheduler.Start();

    await scheduler.ScheduleJob(GetAbsenceJob(), GetDefaultTrigger(nameof(AbsenceJob)));
    await scheduler.ScheduleJob(GetAddressJob(), GetDefaultTrigger(nameof(AddressJob)));
    await scheduler.ScheduleJob(GetCustomerJob(), GetDefaultTrigger(nameof(CustomerJob)));
    await scheduler.ScheduleJob(GetDriverJob(), GetDefaultTrigger(nameof(DriverJob)));
    await scheduler.ScheduleJob(GetPlanCombJob(), GetDefaultTrigger(nameof(PlanCombJob)));
    await scheduler.ScheduleJob(GetPlanGroupJob(), GetDefaultTrigger(nameof(PlanGroupJob)));
    await scheduler.ScheduleJob(GetSupplierJob(), GetDefaultTrigger(nameof(SupplierJob)));
    await scheduler.ScheduleJob(GetTrailerJob(), GetDefaultTrigger(nameof(TrailerJob)));
    await scheduler.ScheduleJob(GetTransportTypeJob(), GetDefaultTrigger(nameof(TransportTypeJob)));
    await scheduler.ScheduleJob(GetVehicleJob(), GetDefaultTrigger(nameof(VehicleJob)));

    await scheduler.ScheduleJob(GetImportFilesJob(), GetImportTrigger(nameof(ImportFilesJob)));
    await scheduler.ScheduleJob(GetExtractFilesJob(), GetImportTrigger(nameof(ExtractFilesJob)));

}

GetSchedular

private static async Task<IScheduler> GetScheduler()
{
    var props = new NameValueCollection {
        { "quartz.threadPool.threadCount",      "20" },
        { "quartz.jobStore.misfireThreshold",   "60000" },
        { "quartz.serializer.type",             "binary" },
        { "quartz.scheduler.instanceName",      "SynchroScheduler"},
        { "quartz.jobStore.type",               "Quartz.Simpl.RAMJobStore, Quartz" },
        { "quartz.threadPool.type",             "Quartz.Simpl.SimpleThreadPool, Quartz" }
    };
    var factory = new StdSchedulerFactory(props);
    var scheduler = await factory.GetScheduler();

    return scheduler;
}

EDIT:

How the service is started:

var host = new HostBuilder()
                .ConfigureHostConfiguration(configHost =>
                {
                    configHost.SetBasePath(Directory.GetCurrentDirectory());
                    configHost.AddJsonFile(Text.Hostsettings, optional: true);
                    configHost.AddEnvironmentVariables(prefix: Text.Prefix);
                    configHost.AddCommandLine(args);
                })
                .ConfigureAppConfiguration((hostContext, configApp) =>
                {
                    configApp.SetBasePath(Directory.GetCurrentDirectory());
                    configApp.AddJsonFile(Text.Appsettings, optional: true);
                    configApp.AddJsonFile($"appsettings.{hostContext.HostingEnvironment.EnvironmentName}.json", optional: true);
                    configApp.AddEnvironmentVariables(prefix: Text.Prefix);
                    configApp.AddCommandLine(args);
                })
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddLogging();
                    services.Configure<SynchroDbOptions>(hostContext.Configuration.GetSection("SynchoDbOptions"));
                    services.AddHostedService<DbSyncService>();
                })
                .ConfigureLogging((hostContext, configLogging) =>
                {
                    configLogging.AddConfiguration(hostContext.Configuration.GetSection("Logging"));
            configLogging.AddConsole();

        })
        .UseConsoleLifetime()
        .Build();

host.RunAsync();

Solution

  • Yes, you're right. ScheduleJob just registers the job, it doesn't wait for it to execute. At some point the garbage collector will come along and reclaim the memory, and as I recall there is a finalizer that de-schedules any jobs. Therefore you need to store the IScheduler instance you're scheduling into.

    One option would be to return the IScheduler to the caller so that they can decide how to store it:

    public async Task<IScheduler> StartAsync(CancellationToken cancellationToken)
    {
        ...
        ...
        return scheduler;
    }
    

    Another is to just have your instance method store it in a member variable and have the class manage the lifetime of the scheduler. Bases on your comments this is probably the best approach.

    As your class DBSyncService is registered as a service then just add a member variable:

    private IScheduler _scheduler;
    

    And set it in StartAsync and that will mean that the lifetime of the scheduler is goverened by the lifetime of the service, which is in turn managed by the lifetime of you service container.