如何在后台的计时器上写入数据库。例如,检查邮件并将新字母添加到数据库。在该示例中,我在写数据库之前就简化了代码。
Microsoft示例中的类名称。 录音课本身:
namespace EmailNews.Services
{
internal interface IScopedProcessingService
{
void DoWork();
}
internal class ScopedProcessingService : IScopedProcessingService
{
private readonly ApplicationDbContext _context;
public ScopedProcessingService(ApplicationDbContext context)
{
_context = context;
}
public void DoWork()
{
Mail mail = new Mail();
mail.Date = DateTime.Now;
mail.Note = "lala";
mail.Tema = "lala";
mail.Email = "lala";
_context.Add(mail);
_context.SaveChangesAsync();
}
}
}
计时器类:
namespace EmailNews.Services
{
#region snippet1
internal class TimedHostedService : IHostedService, IDisposable
{
private readonly ILogger _logger;
private Timer _timer;
public TimedHostedService(IServiceProvider services, ILogger<TimedHostedService> logger)
{
Services = services;
_logger = logger;
}
public IServiceProvider Services { get; }
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is starting.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromMinutes(1));
return Task.CompletedTask;
}
private void DoWork(object state)
{
using (var scope = Services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedProcessingService>();
scopedProcessingService.DoWork();
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
#endregion
}
启动:
services.AddHostedService<TimedHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
似乎一切都像示例中那样完成,但是什么都没有添加到数据库中,不是吗?
答案 0 :(得分:3)
这是一个非常有趣的问题,可以归结为“如何正确处理异步计时器回调?”
眼前的问题是SaveChangesAsync
没有等待。 DbContext几乎可以肯定在SaveChangesAsync
有机会运行之前就被处置掉了。为了等待它,DoWork
必须成为async Task
方法(绝不异步作废):
internal interface IScheduledTask
{
Task DoWorkAsync();
}
internal class MailTask : IScheduledTask
{
private readonly ApplicationDbContext _context;
public MailTask(ApplicationDbContext context)
{
_context = context;
}
public async Task DoWorkAsync()
{
var mail = new Mail
{ Date = DateTime.Now,
Note = "lala",
Tema = "lala",
Email = "lala" };
_context.Add(mail);
await _context.SaveChangesAsync();
}
}
现在的问题是如何从计时器回调中调用DoWorkAsync
。如果我们不等待就调用它,那么我们将首先遇到相同问题。计时器回调无法处理返回Task的方法。我们也不能将其设置为async void
,因为这将导致相同的问题-该方法将在任何异步操作有机会完成之前返回。
David Fowler在其Timer Callbacks的Async Guidance部分中说明了如何正确处理异步计时器回调 文章:
private readonly Timer _timer;
private readonly HttpClient _client;
public Pinger(HttpClient client)
{
_client = new HttpClient();
_timer = new Timer(Heartbeat, null, 1000, 1000);
}
public void Heartbeat(object state)
{
// Discard the result
_ = DoAsyncPing();
}
private async Task DoAsyncPing()
{
await _client.GetAsync("http://mybackend/api/ping");
}
实际方法应为async Task
,但返回的任务只需分配,而不必等待,以便其正常工作。
将此问题应用到问题中会导致如下结果:
public Task StartAsync(CancellationToken cancellationToken)
{
...
_timer = new Timer(HeartBeat, null, TimeSpan.Zero,
TimeSpan.FromMinutes(1));
return Task.CompletedTask;
}
private void Heartbeat(object state)
{
_ = DoWorkAsync();
}
private async Task DoWorkAsync()
{
using (var scope = Services.CreateScope())
{
var schedTask = scope.ServiceProvider
.GetRequiredService<IScheduledTask>();
await schedTask.DoWorkAsync();
}
}
David Fowler解释了ALWAY BAD in ASP.NET Core为何是异步无效的原因-不仅不等待异步操作,而且异常还会使应用程序崩溃。
他还解释了为什么we can't use Timer(async state=>DoWorkAsync(state))
-这是async void
的代表。