* Enhance thread safety with ConcurrentDictionary usage Replaced `IDictionary` with `ConcurrentDictionary` for both `_scheduledTasks` and `_scheduledTaskKeys` to improve thread safety in a multi-threaded environment. Updated methods `RegisterScheduledTask`, `RemoveScheduledTask`, and `RemoveScheduledTasks` to utilize the `Remove` method of `ConcurrentDictionary`, ensuring safe and efficient removal of scheduled tasks. * Refactor task registration and removal logic Updated `RegisterScheduledTask` to use `AddOrUpdate` for streamlined task management. This change simplifies the addition and updating of scheduled tasks by consolidating logic into a single operation. Introduced `RemoveScheduledTask` method to handle task removal by name, improving code organization and clarity. * Improve task removal handling in LocalScheduler Modified the `LocalScheduler` class to enhance the removal process of scheduled tasks from the `_scheduledTaskKeys` collection. The removal operation now captures the result in a variable and includes a conditional check to log a warning if the task was not found, improving error handling and debugging capabilities. * Refactor task removal in LocalScheduler Updated the removal process for scheduled tasks in `_scheduledTasks`. The new implementation collects all corresponding keys and attempts to remove them individually, logging warnings for any failures. This enhances error handling and provides better debugging information. --------- Co-authored-by: Max Brooks <Max@compyl.com>
107 lines
4.1 KiB
C#
107 lines
4.1 KiB
C#
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using Elsa.Extensions;
|
|
|
|
namespace Elsa.Scheduling.Services;
|
|
|
|
/// <summary>
|
|
/// Represents a local, in-memory scheduler that schedules tasks in-process.
|
|
/// </summary>
|
|
public class LocalScheduler : IScheduler
|
|
{
|
|
private readonly IServiceProvider _serviceProvider;
|
|
private readonly ConcurrentDictionary<string, IScheduledTask> _scheduledTasks = new ConcurrentDictionary<string, IScheduledTask>();
|
|
private readonly ConcurrentDictionary<IScheduledTask, ICollection<string>> _scheduledTaskKeys = new ConcurrentDictionary<IScheduledTask, ICollection<string>>();
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="LocalScheduler"/> class.
|
|
/// </summary>
|
|
public LocalScheduler(IServiceProvider serviceProvider)
|
|
{
|
|
_serviceProvider = serviceProvider;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public ValueTask ScheduleAsync(string name, ITask task, ISchedule schedule, CancellationToken cancellationToken = default)
|
|
{
|
|
return ScheduleAsync(name, task, schedule, null, cancellationToken);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public ValueTask ScheduleAsync(string name, ITask task, ISchedule schedule, IEnumerable<string>? keys = null, CancellationToken cancellationToken = default)
|
|
{
|
|
var scheduleContext = new ScheduleContext(_serviceProvider, task);
|
|
var scheduledTask = schedule.Schedule(scheduleContext);
|
|
|
|
RegisterScheduledTask(name, scheduledTask, keys);
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public ValueTask ClearScheduleAsync(string name, CancellationToken cancellationToken = default)
|
|
{
|
|
RemoveScheduledTask(name);
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public ValueTask ClearScheduleAsync(IEnumerable<string> keys, CancellationToken cancellationToken = default)
|
|
{
|
|
RemoveScheduledTasks(keys);
|
|
|
|
return ValueTask.CompletedTask;
|
|
}
|
|
|
|
|
|
private void RegisterScheduledTask(string name, IScheduledTask scheduledTask, IEnumerable<string>? keys = null)
|
|
{
|
|
_scheduledTasks.AddOrUpdate(
|
|
name,
|
|
addValueFactory: _ => scheduledTask,
|
|
updateValueFactory: (_, existingScheduledTask) =>
|
|
{
|
|
existingScheduledTask.Cancel();
|
|
var removed = _scheduledTaskKeys.TryRemove(existingScheduledTask, out ICollection<string>? _);
|
|
if (!removed)
|
|
System.Diagnostics.Debug.WriteLine($"[LocalScheduler] Warning: Tried to remove scheduled task keys for an existing scheduled task, but it was not present in _scheduledTaskKeys.");
|
|
return scheduledTask;
|
|
});
|
|
|
|
if (keys != null)
|
|
_scheduledTaskKeys[scheduledTask] = keys.ToList();
|
|
}
|
|
|
|
|
|
private void RemoveScheduledTask(string name)
|
|
{
|
|
if (_scheduledTasks.TryGetValue(name, out var existingScheduledTask))
|
|
{
|
|
_scheduledTaskKeys.Remove(existingScheduledTask, out _);
|
|
_scheduledTasks.Remove(name, out _);
|
|
existingScheduledTask.Cancel();
|
|
}
|
|
}
|
|
|
|
private void RemoveScheduledTasks(IEnumerable<string> keys)
|
|
{
|
|
foreach (var key in keys)
|
|
{
|
|
var scheduledTasks = _scheduledTaskKeys.Where(x => x.Value.Contains(key)).Select(x => x.Key).Distinct().ToList();
|
|
|
|
foreach (var scheduledTask in scheduledTasks)
|
|
{
|
|
// Collect all keys in _scheduledTasks that map to this scheduledTask
|
|
var matchingTaskKeys = _scheduledTasks.Where(x => x.Value == scheduledTask).Select(x => x.Key).ToList();
|
|
foreach (var taskKey in matchingTaskKeys)
|
|
{
|
|
var removed = _scheduledTasks.TryRemove(taskKey, out _);
|
|
if (!removed)
|
|
System.Diagnostics.Debug.WriteLine($"[LocalScheduler] Warning: Failed to remove scheduled task with key '{taskKey}' for '{key}' from _scheduledTasks.");
|
|
}
|
|
|
|
_scheduledTaskKeys.Remove(scheduledTask, out _);
|
|
scheduledTask.Cancel();
|
|
}
|
|
}
|
|
}
|
|
} |