定时器写入数据库的后台任务
Background task of writing to the database by timer
如何在后台定时写入数据库。例如,检查邮件并将新信件添加到数据库中。在示例中,我在写入数据库之前简化了代码。
Microsoft 示例中的 class 个名称。
录音 class 本身:
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();
}
}
}
计时器class:
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>();
看起来一切都按照示例中的方式完成,但是没有向数据库中添加任何内容,这不是吗?
这是一个相当有趣的问题,归结为 "How do you correctly handle an async timer callback?"
眼前的问题是 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
。如果我们只是调用它而不等待,我们将遇到与最初遇到的 相同的 问题。计时器回调无法处理 return 任务的方法。我们也不能做到 async void
,因为这会导致同样的问题——该方法将在任何异步操作有机会完成之前 return。
David Fowler 在 Timer Callbacks section of his 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
但 returned 任务只需要 分配 ,而不是等待,以便它正常工作.
将此应用于问题会导致类似这样的结果:
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 解释了为什么 async void 是 ALWAY BAD in ASP.NET Core - 不仅不会等待异步操作,异常还会使应用程序崩溃。
他还解释了为什么 we can't use Timer(async state=>DoWorkAsync(state))
- 这是一个 async void
代表。
如何在后台定时写入数据库。例如,检查邮件并将新信件添加到数据库中。在示例中,我在写入数据库之前简化了代码。
Microsoft 示例中的 class 个名称。 录音 class 本身:
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();
}
}
}
计时器class:
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>();
看起来一切都按照示例中的方式完成,但是没有向数据库中添加任何内容,这不是吗?
这是一个相当有趣的问题,归结为 "How do you correctly handle an async timer callback?"
眼前的问题是 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
。如果我们只是调用它而不等待,我们将遇到与最初遇到的 相同的 问题。计时器回调无法处理 return 任务的方法。我们也不能做到 async void
,因为这会导致同样的问题——该方法将在任何异步操作有机会完成之前 return。
David Fowler 在 Timer Callbacks section of his 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
但 returned 任务只需要 分配 ,而不是等待,以便它正常工作.
将此应用于问题会导致类似这样的结果:
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 解释了为什么 async void 是 ALWAY BAD in ASP.NET Core - 不仅不会等待异步操作,异常还会使应用程序崩溃。
他还解释了为什么 we can't use Timer(async state=>DoWorkAsync(state))
- 这是一个 async void
代表。