Nous travaillons avec .NET Core Web Api et recherchons une solution légère pour enregistrer les demandes d'intensité variable dans la base de données, mais nous ne voulons pas que le client attende le processus d'enregistrement.
Malheureusement, aucun HostingEnvironment.QueueBackgroundWorkItem(..)
n'est implémenté dans dnx
, et Task.Run(..)
n'est pas sûr.
Existe-t-il une solution élégante?
Réponses
Trop de publicités?Comme @axelheer mentionné IHostedService est le chemin à parcourir .NET Core 2.0 et au-dessus.
J'avais besoin d'un léger comparables ASP.NET de Base de remplacement pour HostingEnvironment.QueueBackgroundWorkItem, j'ai donc écrit DalSoft.De l'hébergement.BackgroundQueue qui uses.NET Core 2.0 IHostedService.
PM> Install-Package DalSoft.De l'hébergement.BackgroundQueue
Dans votre ASP.NET Noyau de Démarrage.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddBackgroundQueue(onException:exception =>
{
});
}
Pour la file d'attente de Tâche de fond juste ajouter BackgroundQueue
de votre constructeur du contrôleur et appelez - Enqueue
.
public EmailController(BackgroundQueue backgroundQueue)
{
_backgroundQueue = backgroundQueue;
}
[HttpPost, Route("/")]
public IActionResult SendEmail([FromBody]emailRequest)
{
_backgroundQueue.Enqueue(async cancellationToken =>
{
await _smtp.SendMailAsync(emailRequest.From, emailRequest.To, request.Body);
});
return Ok();
}
QueueBackgroundWorkItem
a disparu, mais nous avons IApplicationLifetime
au lieu de IRegisteredObject
, qui est utilisé par l'ancien. Et c'est très prometteur pour de tels scénarios, je pense.
L'idée (et je ne suis pas encore tout à fait sûr, si c'est un assez mauvais; donc, méfiez-vous!) est d'enregistrer un singleton, qui génère et observe de nouvelles tâches. Au sein de ce singleton on peut d'ailleurs créer un "arrêté de l'événement" pour la bonne attendent toujours en cours d'exécution des tâches.
Ce "concept" pourrait être utilisé pour de courtes exécution des trucs comme l'exploitation forestière, l'envoi des mails, et autres. Les choses, qui ne devrait pas prendre beaucoup de temps, mais serait de produire des retards inutiles pour la requête en cours.
public class BackgroundPool
{
protected ILogger<BackgroundPool> Logger { get; }
public BackgroundPool(ILogger<BackgroundPool> logger, IApplicationLifetime lifetime)
{
if (logger == null)
throw new ArgumentNullException(nameof(logger));
if (lifetime == null)
throw new ArgumentNullException(nameof(lifetime));
lifetime.ApplicationStopped.Register(() =>
{
lock (currentTasksLock)
{
Task.WaitAll(currentTasks.ToArray());
}
logger.LogInformation(BackgroundEvents.Close, "Background pool closed.");
});
Logger = logger;
}
private readonly object currentTasksLock = new object();
private readonly List<Task> currentTasks = new List<Task>();
public void SendStuff(Stuff whatever)
{
var task = Task.Run(async () =>
{
Logger.LogInformation(BackgroundEvents.Send, "Sending stuff...");
try
{
// do THE stuff
Logger.LogInformation(BackgroundEvents.SendDone, "Send stuff returns.");
}
catch (Exception ex)
{
Logger.LogError(BackgroundEvents.SendFail, ex, "Send stuff failed.");
}
});
lock (currentTasksLock)
{
currentTasks.Add(task);
currentTasks.RemoveAll(t => t.IsCompleted);
}
}
}
Un tel BackgroundPool
doit être enregistré comme un singleton et peut être utilisé par n'importe quel autre composant de la via DI. Je suis en train de l'utiliser pour l'envoi de mails et il fonctionne très bien (testé envoi de mail pendant l'application de l'arrêt trop).
Remarque: accès à des trucs comme le courant HttpContext
au sein de la tâche en arrière-plan ne devrait pas travailler. La vieille solution utilise UnsafeQueueUserWorkItem
d'interdire que de toute façon.
Qu'en pensez-vous?
Mise à jour:
Avec ASP.NET Core 2.0 il y a des nouveaux trucs pour les tâches en arrière-plan, ce qui est mieux avec ASP.NET de Base 2.1: mise en Œuvre de tâches en arrière-plan dans .NET Core 2.x webapps ou microservices avec IHostedService et la BackgroundService classe
Vous pouvez utiliser Hangfire ( http://hangfire.io/ ) pour les tâches d'arrière-plan dans .NET Core.
Par exemple :
var jobId = BackgroundJob.Enqueue(
() => Console.WriteLine("Fire-and-forget!"));
Voici une version modifiée de la réponse d' Axel qui vous permet de passer des délégués et d'effectuer un nettoyage plus agressif des tâches terminées.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Logging;
namespace Example
{
public class BackgroundPool
{
private readonly ILogger<BackgroundPool> _logger;
private readonly IApplicationLifetime _lifetime;
private readonly object _currentTasksLock = new object();
private readonly List<Task> _currentTasks = new List<Task>();
public BackgroundPool(ILogger<BackgroundPool> logger, IApplicationLifetime lifetime)
{
if (logger == null)
throw new ArgumentNullException(nameof(logger));
if (lifetime == null)
throw new ArgumentNullException(nameof(lifetime));
_logger = logger;
_lifetime = lifetime;
_lifetime.ApplicationStopped.Register(() =>
{
lock (_currentTasksLock)
{
Task.WaitAll(_currentTasks.ToArray());
}
_logger.LogInformation("Background pool closed.");
});
}
public void QueueBackgroundWork(Action action)
{
#pragma warning disable 1998
async Task Wrapper() => action();
#pragma warning restore 1998
QueueBackgroundWork(Wrapper);
}
public void QueueBackgroundWork(Func<Task> func)
{
var task = Task.Run(async () =>
{
_logger.LogTrace("Queuing background work.");
try
{
await func();
_logger.LogTrace("Background work returns.");
}
catch (Exception ex)
{
_logger.LogError(ex.HResult, ex, "Background work failed.");
}
}, _lifetime.ApplicationStopped);
lock (_currentTasksLock)
{
_currentTasks.Add(task);
}
task.ContinueWith(CleanupOnComplete, _lifetime.ApplicationStopping);
}
private void CleanupOnComplete(Task oldTask)
{
lock (_currentTasksLock)
{
_currentTasks.Remove(oldTask);
}
}
}
}