.NET Core中HostingEnvironment.QueueBackgroundWorkItem的替代解决方案

Not*_*who 44 background-process asp.net-core-mvc .net-core dnx asp.net-core

我们正在使用.NET Core Web Api,并寻找一种轻量级解决方案,将具有可变强度的请求记录到数据库中,但不希望客户端等待保存过程.
不幸的是,没有HostingEnvironment.QueueBackgroundWorkItem(..)实施dnx,Task.Run(..)也不安全.
有没有优雅的解决方案?

Axe*_*eer 15

QueueBackgroundWorkItem已经消失了,但我们已经IApplicationLifetime取而代之了IRegisteredObject,前者正在使用它.我认为,这种情况看起来很有希望.

这个想法(我仍然不太确定,如果它是一个非常糟糕的那个;因此,要小心!)是注册一个单例,它产生观察新的任务.在该单例内,我们还可以注册"已停止的事件",以便正确等待仍在运行的任务.

这个"概念"可以用于诸如日志记录,邮件发送等的短期运行之类的东西.事情,这不应该花费太多时间,但会对当前的请求产生不必要的延迟.

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这样的a BackgroundPool应该被注册为单身,并且可以由任何其他组件通过DI使用.我目前正在使用它发送邮件,它工作正常(在应用程序关闭期间测试邮件发送).

注意:访问HttpContext后台任务中的当前内容等功能不起作用.在老办法使用UnsafeQueueUserWorkItem,禁止该反正.

你怎么看?

更新:

使用ASP.NET Core 2.0可以获得后台任务的新功能,使用ASP.NET Core 2.1可以更好:使用IHostedService和BackgroundService类在.NET Core 2.x webapps或微服务中实现后台任务


Dal*_*oft 12

正如@axelheer所提到的,IHostedService是进入.NET Core 2.0及更高版本的方法.

我需要一个轻量级的东西进行ASP.NET核心更换为HostingEnvironment.QueueBackgroundWorkItem,所以我写了DalSoft.Hosting.BackgroundQueue这uses.NET核心的2.0 IHostedService.

PM> Install-Package DalSoft.Hosting.BackgroundQueue

在ASP.NET Core Startup.cs中:

public void ConfigureServices(IServiceCollection services)
{
   services.AddBackgroundQueue(onException:exception =>
   {

   });
}
Run Code Online (Sandbox Code Playgroud)

要排队后台任务,只需添加BackgroundQueue到控制器的构造函数并调用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();
}
Run Code Online (Sandbox Code Playgroud)

  • 抱歉,大家看到[排队后台任务](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&amp;tabs=visual-studio#queued-background-任务)MS 文档发生了一些变化。如果您仍然认为包装库值得,请告诉我。 (3认同)
  • 是的,但我会看看 Microsoft.NET.Sdk.Worker,因为它可以完成我的包所做的所有事情,甚至更多。 (2认同)

ycr*_*lle 9

您可以将Hangfire(http://hangfire.io/)用于.NET Core中的后台作业.

例如 :

var jobId = BackgroundJob.Enqueue(
    () => Console.WriteLine("Fire-and-forget!"));
Run Code Online (Sandbox Code Playgroud)

  • 此解决方案需要 SQL Server。并不是说它_不能_取代你可能用“HostingEnvironment.QueueBackgroundWorkItem”做的一些事情,但它是一个明显“更重”的解决方案,我认为在这里值得一提。 (5认同)

Sco*_*ain 5

以下是Axel答案的调整版本,可让您传递代理并对已完成的任务进行更积极的清理.

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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)