67 votes

Solution alternative à HostingEnvironment.QueueBackgroundWorkItem dans .NET Core

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?

23voto

DalSoft Points 1827

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();
}

16voto

Axel Heer Points 1375

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

11voto

ycrumeyrolle Points 158

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!"));
 

6voto

Scott Chamberlain Points 32782

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);
            }
        }
    }
}
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X