我想创建一个可以通过用户交互启动和停止的托管服务。该服务不应该自动启动,在它已经启动时尝试启动它应该没有效果,并且应该可以检查该服务当前是否正在运行。该服务最终将完成运行,此时它应该转换到停止状态,直到它再次启动。
下面是我实现这一点的尝试,它似乎正在工作,但我想知道这种方法是否存在任何明显的缺陷,或者是否有更简单的解决方案。
有两个部分,一个FooJobControl
可以注入到控制器中以启动或停止服务的单例,以及FooJob
等待来自的信号FooJobControl
开始工作并在它停止时发出信号的托管服务。
在 Startup.cs
services.AddHostedService<FooJob>();
services.AddSingleton<FooJobControl>();
FooJobControl.cs
public class FooJobControl
{
private AsyncAutoResetEvent _evt = new AsyncAutoResetEvent(false);
private CancellationTokenSource _cts;
private object _sync = new object();
public void Start()
{
lock (_sync)
{
if (_cts == null)
{
_evt.Set();
}
}
}
public void Stop()
{
lock (_sync)
{
if (_cts != null)
{
_cts.Cancel();
}
}
}
public bool IsRunning
{
get
{
lock (_sync)
{
return _cts != null && !_cts.IsCancellationRequested;
}
}
}
public async Task<CancellationToken> JobStart(CancellationToken stoppingToken)
{
await _evt.WaitAsync(stoppingToken);
lock (_sync)
{
_cts = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken);
return _cts.Token;
}
}
public void JobEnd()
{
lock (_sync)
{
_cts = null;
}
}
}
FooJob.cs
public class FooJob : BackgroundService
{
private FooJobControl _control;
private ILogger<FooJob> _logger;
public FooJob(ILogger<FooJob> logger, FooJobControl control)
{
_logger = logger;
_control = control;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (true)
{
CancellationToken token = await _control.JobStart(stoppingToken);
try
{
await DoWork(token);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to run job");
}
_control.JobEnd();
}
}
private async Task DoWork(CancellationToken token)
{
for (int i = 1; i <= 10; i++)
{
if (token.IsCancellationRequested)
{
_logger.LogInformation("DoWork Cancelled");
return;
}
_logger.LogInformation("DoWork " + i);
await Task.Delay(500);
}
}
}