Ответ 1
Вы все еще можете использовать IHostedService
в качестве базы для фоновых задач в сочетании с BlockingCollection
.
Создайте оболочку для BlockingCollection
, чтобы вы могли добавить ее как синглтон.
public class TasksToRun
{
private readonly BlockingCollection<TaskSettings> _tasks;
public TasksToRun() => _tasks = new BlockingCollection<TaskSettings>();
public void Enqueue(TaskSettings settings) => _tasks.Add(settings);
public TaskSettings Dequeue(CancellationToken token) => _tasks.Take(token);
}
Затем в реализации IHostedService
"слушайте" задачи, а когда задачи "прибывают", выполняйте его.
BlockingCollection
остановит выполнение, если коллекция пуста, поэтому ваш цикл while
не будет занимать процессорное время.
Метод .Take
принимает cancellationToken
в качестве аргумента. С помощью токена вы можете отменить "ожидание" следующей задачи, когда приложение остановится.
public class BackgroundService : IHostedService
{
private readonly TasksToRun _tasks;
private CancellationTokenSource _tokenSource;
private Task _currentTask;
public BackgroundService(TasksToRun tasks) => _tasks = tasks;
public async Task StartAsync(CancellationToken cancellationToken)
{
_tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
while (cancellationToken.IsCancellationRequested == false)
{
try
{
var taskToRun = _tasks.Dequeue(_tokenSource.Token);
// We need to save executable task,
// so we can gratefully wait for it completion in Stop method
_currentTask = ExecuteTask(taskToRun);
await _currentTask;
}
catch (OperationCanceledException)
{
// execution cancelled
}
}
}
public async Task StopAsync(CancellationToken cancellationToken)
{
_tokenSource.Cancel(); // cancel "waiting" for task in blocking collection
if (_currentTask == null) return;
// wait when _currentTask is complete
await Task.WhenAny(_currentTask, Task.Delay(-1, cancellationToken));
}
}
А в контроллере вы просто добавляете задачу, которую хотите запустить, в нашу коллекцию
public class JobController : Controller
{
private readonly TasksToRun _tasks;
public JobController(TasksToRun tasks) => _tasks = tasks;
public IActionResult PostJob()
{
var settings = CreateTaskSettings();
_tasks.Enqueue(settings);
return Ok();
}
}
Оболочка для блокировки коллекции должна быть зарегистрирована для внедрения зависимостей как синглтон
services.AddSingleton<TasksToRun, TasksToRun>();
Зарегистрировать фоновую службу
services.AddHostedService<BackgroundService>();