Je développe une application Web avec un REST Api utilisant C # avec asp.net core 2.0
Ce que je veux réaliser, c'est lorsque le client envoie une demande à un point de terminaison. J'exécute une tâche en arrière-plan séparée du contexte de la demande du client, qui sera terminé si la tâche a démarré correctement.
Je sais qu'il existe HostedService, mais le problème est que HostedService démarre au démarrage du serveur. Autant que je sache, il n'existe aucun moyen de démarrer HostedService manuellement à partir d'un contrôleur.
Voici un code simple qui illustre la question.
[Authorize(AuthenticationSchemes = "UsersScheme")]
public class UsersController : Controller
{
[HttpPost]
public async Task<JsonResult> StartJob([FromForm] string UserId, [FromServices] IBackgroundJobService backgroundService) {
//check user account
(bool isStarted, string data) result = backgroundService.Start();
return JsonResult(result);
}
}
Vous pouvez toujours utiliser IHostedService
comme base pour les tâches en arrière-plan en combinaison avec BlockingCollection
.
Créez un wrapper pour BlockingCollection
afin de pouvoir l'injecter en tant que singleton.
public class TasksToRun
{
private readonly BlockingCollection<TaskSettings> _tasks;
public TasksToRun() => _tasks = new BlockingCollection<TaskSettings>();
public Enqueue(TaskSettings settings) => _tasks.Add(settings);
public Dequeue(CancellationToken token) => _tasks.Take(token);
}
Ensuite, dans la mise en œuvre de IHostedService
, "écoutez" les tâches et lorsque les tâches "arrivent", exécutez-le.BlockingCollection
arrêtera l'exécution si la collection est vide - votre boucle while
ne consommera donc pas de temps processeur..Take
méthode accepte cancellationToken
comme argument. Avec token, vous pouvez annuler l'attente de la tâche suivante lorsque l'application s'arrête.
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's 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));
}
}
Et dans le contrôleur, vous ajoutez simplement la tâche que vous souhaitez exécuter à notre collection
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();
}
}
Le wrapper pour bloquer la collecte doit être enregistré pour l'injection de dépendance en tant que singleton
services.AddSingleton<TasksToRun, TasksToRun>();
Enregistrer le service d'arrière-plan
services.AddHostedService<BackgroundService>();
Microsoft a documenté la même chose à l’adresse https://docs.Microsoft.com/en-us/aspnet/core/fundamentals/Host/hosted-services?view=aspnetcore-2.1
Il accomplit l'utilisation de BackgroundTaskQueue, qui obtient le travail assigné par Controller et le travail est effectué par QueueHostedService qui dérive de BackgroundService.