我需要一个将以一定频率执行的后台服务。我还需要能够唤醒这项服务。为此,我使用
BackgroundService
和 AutoResetEvent
。完整代码如下
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<WakeUpService>();
builder.Services.AddHostedService<CustomBackgroundService>();
var app = builder.Build();
app.MapWhen(context => context.Request.Path.StartsWithSegments("/WakeUp", StringComparison.OrdinalIgnoreCase), appBuilder =>
{
appBuilder.Run(async context =>
{
context.RequestServices.GetRequiredService<WakeUpService>().WakeUp();
await context.Response.WriteAsync("WakeUp: " + DateTime.Now);
});
});
app.MapWhen(_ => true, appBuilder =>
{
appBuilder.Run(async context => { await context.Response.WriteAsync("OK: " + DateTime.Now); });
});
app.Run();
public class CustomBackgroundService(WakeUpService wakeUpService) : BackgroundService
{
private int _counter;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await InternalWorkAsync();
wakeUpService.Delay();
}
}
private Task InternalWorkAsync()
{
Console.WriteLine("Counter: " + _counter++);
return Task.CompletedTask;
}
}
public class WakeUpService
{
private AutoResetEvent ResetEvent { get; } = new(false);
public void WakeUp() => ResetEvent.Set();
public void Delay() => ResetEvent.WaitOne(3000);
}
问题是,尽管服务本身启动并工作,但端点(/WakeUp 或任何其他端点)不起作用。但如果我将
ExecuteAsync
包裹在 Task.Run
中,一切都会开始工作。
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.Run(async () =>
{
while (!stoppingToken.IsCancellationRequested)
{
await InternalWorkAsync();
wakeUpService.Delay();
}
});
}
为什么会发生这种情况?
Task.Run
的方法有多可靠?
来自 文档 以及 open Github 问题
在
变为 异步 之前,不会启动更多服务,例如通过调用ExecuteAsync
。避免在await
中执行长时间、阻塞的初始化工作。ExecuteAsync
是
AutoResetEvent
挡住了这里。
诀窍是尽快变得异步。
提到的 Github 问题的建议是致电
await Task.Yield()
。
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.Yield();
while (!stoppingToken.IsCancellationRequested)
{
await InternalWorkAsync();
wakeUpService.Delay();
}
}