Closed soend closed 5 years ago
For testing i tried tried injecting IScheduler and adding new task to it and it actually worked. Code what i tried:
public ScheduleOrderService(ILogger<ScheduleOrderService> logger, IOptions<AppConfig> config, IScheduler scheduler)
{
_logger = logger;
_config = config.Value;
scheduler.Schedule(() => _logger.LogInformation("foobar") ).EveryMinute();
}
If there's a way to hook into some event that's emited when the config files change, here's what you could do:
When scheduling, add an ID to each scheduled event (this is an internal method, which maybe I should make public via the interface...)
scheduler.EveryMinute().AssignUniqueIndentifier("SomeID");
Keep track of all the IDS that you have (statically).
Then, when you need to refresh - do this for each ID:
scheduler.TryUnschedule("SomeID");
Then re-schedule again.
Those are thread safe methods too.
Not necessarily recommended for production, but would work in dev for sure (assuming you can detect when the config files change - which Im looking into)
So you can detect when the IConfiguration is changed.
Take a look here https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.configuration.iconfiguration.getreloadtoken?view=aspnetcore-2.2
There will be actually just one scheduled task ever created what i would like to change the execution time on. So if there would be a way to unschedule it and schedule new one that should do the business.
So try in Configure()
ChangeToken.OnChange(() => this.Configuration.GetReloadToken(), () => {
using (var scope = app.ApplicationServices.CreateScope())
{
IScheduler scheduler = scope.ServiceProvider.GetRequiredService<IScheduler>();
IConfiguration config = scope.ServiceProvider.GetRequiredService<IConfiguration>();
// Remove your past scheduled event/task
(scheduler as Scheduler).TryUnschedule("SomeID");
// Add a new one.
var scheduledEvent = scheduler
.Schedule(() => Console.WriteLine("A new scheduled task!"))
.Cron(config["MyCronConfigValue"]);
// Assign a unique ID so you can remove it next time.
(scheduledEvent as ScheduledEvent).AssignUniqueIndentifier("SomeID");
//Or
scheduledEvent.PreventOverlapping("SomeID");
}
});
This can stay closed, but in case anyone is looking for a generic way to update scheduled tasks using reflection;
/// <summary>
/// Update the schedule for a specific service endpoint.
/// </summary>
/// <param name="fullServiceName">Fully qualified service name</param>
/// <param name="cron">string representation of a CRON schedule (5 values)</param>
/// <param name="scheduler">[FromServices] so not required</param>
/// <returns>string of result</returns>
public async Task<IResult> UpdateServiceSchedule(string fullServiceName, string cron, [FromServices] IScheduler scheduler)
{
_log.LogInformation($"UpdateServiceSchedule called for {fullServiceName} setting CRON to: [{cron}]");
if (string.IsNullOrEmpty(fullServiceName))
return Results.BadRequest("Service Name is required");
// Validate the cron
if (string.IsNullOrEmpty(cron))
return Results.BadRequest("CRON is required");
else if (!CronSchedules.IsValidCRONSchedule(cron))
return Results.BadRequest("CRON is not valid");
// Try to reschedule as per;
// https://github.com/jamesmh/coravel/issues/77#issuecomment-460785919
try
{
// Remove your past scheduled event/task
var unscheduleSuccess = (scheduler as Scheduler)!.TryUnschedule(fullServiceName);
if (!unscheduleSuccess)
{
var errorMessage = $"Error unscheduling service: {fullServiceName}";
_log.LogError(errorMessage);
return Results.BadRequest(errorMessage);
}
// Check we have configured a class for this service
var serviceType = Type.GetType("ServiceBroker.ScheduledInvocables." + fullServiceName);
if (serviceType == null)
{
var errorMessage = $"Error finding service type for scheduling: {fullServiceName}";
_log.LogError(errorMessage);
return Results.BadRequest(errorMessage);
}
// Check if the serviceType implements the IInvocable interface
Type interfaceType = typeof(IInvocable);
bool implementsInterface = serviceType.GetInterfaces().Contains(interfaceType);
if(!implementsInterface)
{
var errorMessage = $"Service type {fullServiceName} exists but is not invocable for scheduling";
_log.LogError(errorMessage);
return Results.BadRequest(errorMessage);
}
// Add a new cron schedule for the type we want to re-schedule
var scheduledEvent = scheduler
.ScheduleInvocableType(serviceType)
.Cron(cron);
// Assign a unique Id so you can remove and update it next time.
var scheduleEventConfig = (scheduledEvent as ScheduledEvent)!.AssignUniqueIndentifier(fullServiceName);
scheduleEventConfig = scheduledEvent.PreventOverlapping(fullServiceName);
// We did it!
var successMessage = $"UpdateServiceSchedule updated {fullServiceName} CRON setting to: [{cron}]";
_log.LogInformation(successMessage);
return Results.Ok(successMessage);
}
catch (Exception ex)
{
var exceptionMessage = $"Error updating service schedule: {fullServiceName} CRON setting to: [{cron}]";
_log.LogError(ex, exceptionMessage);
return Results.BadRequest(exceptionMessage);
}
}
Many thanks to James for ScheduleInvocableType(serviceType)
option!
I would like to add tasks to scheduler dynamically in a service what gets the execution times from configuration file. At the moment its done in the application Configure method and every time configuration is changed i would have to restart the whole program. Is this something what could be possible with current scheduler implementation?