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



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

那么,我还有什么其他选择,我希望不必依赖像Hangfire这样的第三方图书馆?我使用的是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.");
}
}
}

您可以尝试将异步队列与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);
}

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

public class JobInfo
{
public string EmailAddress { get; set; }
public string Body { get; set; }
}

后台队列示例(灵感来自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;
}
}

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

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

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

第页。S-回答过山车和飞行V-

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

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

如果你的网站晚餐不忙,工作也不重要,这是最简单的解决方案。只需确保捕获并记录员工内部的错误(在本例中为EmailOffline(。

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

使用Hangfire,它的后台方法功能非常好,并免费为您提供了一个漂亮的仪表板:https://docs.hangfire.io/en/latest/background-methods/index.html

最新更新