问题描述
如何在后台的计时器上写入数据库.例如,检查邮件并向数据库中添加新字母.在该示例中,我在写数据库之前就简化了代码.
How to write to the database on a timer in the background. For example, check mail and add new letters to the database. In the example, I simplified the code just before writing to the database.
Microsoft示例中的类名称.录音课本身:
The class names from the example in Microsoft.The recording class itself:
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>();
似乎一切都像示例中那样完成,但是什么都没有添加到数据库中,不是吗?
It seems everything is done as in the example, but nothing is added to the database, which is not so?
推荐答案
这是一个非常有趣的问题,可以归结为如何正确处理异步计时器回调?"
This is a rather interesting question, that boils down to "How do you correctly handle an async timer callback?"
直接的问题是SaveChangesAsync
没有等待. DbContext几乎可以肯定在SaveChangesAsync
有机会运行之前就被处置掉了.要等待它,DoWork
必须成为一个async Task
方法(绝不异步作废):
The immediate problem is that SaveChangesAsync
isn't getting awaited. The DbContext almost certainly gets disposed before SaveChangesAsync
has a chance to run. To await it, DoWork
must become an async Task
method (never async void) :
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
.如果我们不等待就调用它,那么我们将首先遇到 same 问题.计时器回调无法处理返回Task的方法.我们也不能将其设置为async void
,因为这将导致相同的问题-该方法将在任何异步操作有机会完成之前返回.
The problem now is how to call DoWorkAsync
from the timer callback. If we just call it without awaiting, we'll get the same problem we had in the first place. A timer callback can't handle methods that return Task. We can't make it async void
either, because this would result in the same problem - the method will return before any async operation has a chance to finish.
David Fowler在中解释了如何正确处理异步计时器回调他的异步指导中的计时器回调部分 文章:
David Fowler explains how to properly handle asynchronous timer callbacks in the Timer Callbacks section of his Async Guidance article :
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
,但返回的任务只需进行分配,而无需等待,以便其正常工作.
The actual method should be async Task
but the returned task only has to be assigned, not awaited, in order for it to work properly.
将此问题应用到问题中会导致如下结果:
Applying this to the question leads to something like this :
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解释了为什么异步无效 总是错误在ASP.NET Core中-不仅不等待异步操作,而且异常还会使应用程序崩溃.
David Fowler explains why async void is ALWAY BAD in ASP.NET Core - it's not only that async actions won't be awaited, exceptions will crash the application.
他还解释了为什么我们不能使用 Timer(async state=>DoWorkAsync(state))
-这是async void
委托.
He also explains why we can't use Timer(async state=>DoWorkAsync(state))
- that's an async void
delegate.
这篇关于通过计时器写入数据库的后台任务的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!