在asp.net core 3.x中按需运行后台任务

Yeh*_*lam 7 c# asp.net background-service asp.net-core

每当我从我的 api 端点收到某个请求时,我都会尝试按需启动后台任务。所有任务所做的就是发送一封电子邮件,延迟 30 秒。所以我虽然BackgroundService会适合。但问题是它看起来BackgroundService主要用于重复性任务,而不是根据此答案按需执行。

那么我还有什么其他选择,我希望不必依赖像 Hangfire 这样的 3rd 方库?我正在使用 asp.net 核心 3.1。

这是我的后台服务。

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace ProjectX.Services {
    public  class EmailOfflineService : BackgroundService {

        private readonly ILogger<EmailOfflineService> log;
        private readonly EmailService emailService;
        public EmailOfflineService(
            ILogger<EmailOfflineService> log, 
            EmailService emailService
        ) {
            this.emailService = emailService;
            this.log = log;
        }

        protected async override Task ExecuteAsync(CancellationToken stoppingToken)
        {

            log.LogDebug("Email Offline Service Starting...");
            stoppingToken.Register(() => log.LogDebug("Email Offline Service is stopping."));

            while(!stoppingToken.IsCancellationRequested)
            {
                // wait for 30 seconds before sending
                await Task.Delay(1000 * 30, stoppingToken);

                await emailService.EmailOffline();
                
                // End the background service
                break;
            }
            log.LogDebug("Email Offline Service is stoped.");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Fed*_*uma 12

您可以尝试将异步队列与BackgroundService.

public class BackgroundEmailService : BackgroundService
{
    private readonly IBackgroundTaskQueue _queue;

    public BackgroundEmailService(IBackgroundTaskQueue queue)
    {
        _queue = queue;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var job = await _queue.DequeueAsync(stoppingToken);
            
            _ = ExecuteJobAsync(job, stoppingToken);
        }
    }

    private async Task ExecuteJobAsync(JobInfo job, CancellationToken stoppingToken)
    {
        try
        {
            await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
            // todo send email
        }
        catch (Exception ex)
        {
            // todo log exception
        }
    }
}

public interface IBackgroundTaskQueue
{
    void EnqueueJob(JobInfo job);

    Task<JobInfo> DequeueAsync(CancellationToken cancellationToken);
}
Run Code Online (Sandbox Code Playgroud)

通过这种方式,您可以IBackgroundTaskQueue在控制器内部注入并将作业排入队列,同时JobInfo将包含一些在后台执行作业的基本信息,例如:

public class JobInfo
{
    public string EmailAddress { get; set; }
    public string Body { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

后台队列示例(受ASP.NET Core 文档启发):

public class BackgroundTaskQueue : IBackgroundTaskQueue
{
    private ConcurrentQueue<JobInfo> _jobs = new ConcurrentQueue<JobInfo>();
    private SemaphoreSlim _signal = new SemaphoreSlim(0);

    public void EnqueueJob(JobInfo job)
    {
        if (job == null)
        {
            throw new ArgumentNullException(nameof(job));
        }

        _jobs.Enqueue(job);
        _signal.Release();
    }

    public async Task<JobInfo> DequeueAsync(CancellationToken cancellationToken)
    {
        await _signal.WaitAsync(cancellationToken);
        _jobs.TryDequeue(out var job);

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


ch_*_*h_g 5

我认为最简单的方法是在处理发送电子邮件请求的代码中进行“即发即忘”调用,如下所示 -

//all done, time to send email
Task.Run(async () => 
{
    await emailService.EmailOffline(emailInfo).ConfigureAwait(false); //assume all necessary info to send email is saved in emailInfo
});
Run Code Online (Sandbox Code Playgroud)

这将启动一个线程来发送电子邮件。该代码将立即返回给调用者。在您的 EmailOffline 方法中,您可以根据需要包含时间延迟逻辑。确保其中也包含错误日志记录逻辑,否则来自 EmailOffline 的异常可能会被默默吞没。

PS - 对 Coastpear 和 FlyingV 的回答 -

无需关心调用上下文的结束。这项工作将在一个单独的线程上完成,该线程完全独​​立于调用上下文。

我在生产中使用类似的机制已经有几年了,到目前为止为零问题。

如果您的站点不是非常繁忙,并且工作并不重要,那么这是最简单的解决方案。只需确保您捕获并记录工作线程内部的错误(本例中为 EmailOffline)。

如果您需要更可靠的解决方案,我建议使用像AWS SQS这样成熟的队列产品,不必费心自己创建一个。创建一个真正好的队列系统并不是一件容易的事。