尝试大致遵循MSDN,我在StartUp
类的作用域服务之后添加了一个托管服务。
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IUtilityService, UtilityService>();
services.AddHostedService<StartupService>();
...
}
我已经这样实现了StartAsync
。
public class StartupService : IHostedService
{
private IServiceProvider Provider { get; }
public StartupService(IServiceProvider provider)
{
Provider = provider;
}
public Task StartAsync(CancellationToken cancellationToken)
{
IServiceScope scope = Provider.CreateScope();
IUtilityService service = scope.ServiceProvider
.GetRequiredService<IUtilityService>();
service.Seed();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
我读过很多文章和博客,但是我无法理解在方法结束时应该返回什么。它现在似乎可以工作,但我可以清楚地看到,我违反了不使用异步调用和返回一个虚拟的想法(甚至不是在停止!)所以我可以安全地得出结论,我做错了(虽然不明显,但我确信它将来会咬我的后面)。
我应该在实现中返回什么,以确保我正在"使用"。不反对框架?
StartAsync需要返回一个任务,该任务可能正在运行,也可能不正在运行(但理想情况下它应该正在运行,这是HostedService的意义所在——一个在应用程序的生命周期内运行的操作/任务,或者只是在比正常情况长一些的时间内运行)。
看起来你正在尝试使用hostdservice执行额外的启动项,而不是仅仅尝试运行一个将在应用程序的整个生命周期中持续的任务/操作。
如果是这种情况,您可以有一个非常简单的设置。你想从StartAsync()方法返回的东西是一个Task。当你返回Task时。CompletedTask,你说的是工作已经完成,没有代码执行-任务已经完成。你想要返回的是在Task对象中运行的额外启动项的代码。在asp.net中,关于hostdservice的好处是,任务运行多长时间并不重要(因为它意味着在应用程序的整个生命周期中运行任务)。
在代码示例之前有一个重要的注意事项-如果你在任务中使用作用域服务,那么你需要使用IServiceScopeFactory生成一个作用域,请阅读这篇StackOverflow文章如果你重构你的服务方法来返回一个任务,你可以只返回它:
public Task StartAsync(CancellationToken)
{
IServiceScope scope = Provider.CreateScope();
IUtilityService service = scope.ServiceProvider
.GetRequiredService<IUtilityService>();
// If Seed returns a Task
return service.Seed();
}
如果你有多个服务方法都返回一个任务,你可以返回一个等待所有任务完成的任务
public Task StartAsync(CancellationToken)
{
IServiceScope scope = Provider.CreateScope();
IUtilityService service = scope.ServiceProvider
.GetRequiredService<IUtilityService>();
ISomeOtherService someOtherService = scope.ServiceProvider
.GetRequiredService<ISomeOtherService>();
var tasks = new List<Task>();
tasks.Add(service.Seed());
tasks.Add(someOtherService.SomeOtherStartupTask());
return Task.WhenAll(tasks);
}
如果你的启动任务做了很多CPU绑定的工作,只需返回Task.Run(() =>{});
public Task StartAsync(CancellationToken)
{
// Return a task which represents my long running cpu startup work...
return Task.Run(() => {
IServiceScope scope = Provider.CreateScope();
IUtilityService service = scope.ServiceProvider
.GetRequiredService<IUtilityService>();
service.LongRunningCpuStartupMethod1();
service.LongRunningCpuStartupMethod2();
}
}
要使用取消令牌,下面的一些示例代码展示了如何通过在Try/Catch中捕获TaskCanceledException并强制退出正在运行的循环来实现。
然后我们继续讨论将在整个应用程序生命周期中运行的任务。这是我用于所有hostdservice实现的基类,它被设计成在应用程序关闭之前永远不会停止运行。
public abstract class HostedService : IHostedService
{
// Example untested base class code kindly provided by David Fowler: https://gist.github.com/davidfowl/a7dd5064d9dcf35b6eae1a7953d615e3
private Task _executingTask;
private CancellationTokenSource _cts;
public Task StartAsync(CancellationToken cancellationToken)
{
// Create a linked token so we can trigger cancellation outside of this token's cancellation
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
// Store the task we're executing
_executingTask = ExecuteAsync(_cts.Token);
// If the task is completed then return it, otherwise it's running
return _executingTask.IsCompleted ? _executingTask : Task.CompletedTask;
}
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
// Stop called without start
if (_executingTask == null)
{
return;
}
// Signal cancellation to the executing method
_cts.Cancel();
// Wait until the task completes or the stop token triggers
await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken));
// Throw if cancellation triggered
cancellationToken.ThrowIfCancellationRequested();
}
// Derived classes should override this and execute a long running method until
// cancellation is requested
protected abstract Task ExecuteAsync(CancellationToken cancellationToken);
}
在这个基类中,您将看到当StartAsync被调用时,我们调用ExecuteAsync()方法,该方法返回一个包含while循环的Task - Task将不会停止运行,直到我们的取消令牌被触发,或者应用程序优雅/强制停止。
ExecuteAsync()方法需要由继承这个基类的任何类来实现,这个基类应该是所有的hostdservice的。
下面是一个示例hostdservice实现,它继承了这个基类,设计为每30秒检入一次。您将注意到ExecuteAsync()方法进入while循环并且永远不会退出—它将每秒"滴答"一次,这是您可以调用其他方法的地方,例如定期检入到另一个服务器。此循环中的所有代码都在Task中返回给StartAsync()并返回给调用者。在while循环退出、应用程序终止或触发取消令牌之前,任务不会终止。
public class CounterHostedService : HostedService
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILog _logger;
public CounterHostedService(IServiceScopeFactory scopeFactory, ILog logger)
{
_scopeFactory = scopeFactory;
_logger = logger;
}
// Checkin every 30 seconds
private int CheckinFrequency = 30;
private DateTime CheckedIn;
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
int counter = 0;
var runningTasks = new List<Task>();
while (true)
{
// This loop will run for the lifetime of the application.
// Time since last checkin is checked every tick. If time since last exceeds the frequency, we perform the action without breaking the execution of our main Task
var timeSinceCheckin = (DateTime.UtcNow - CheckedIn).TotalSeconds;
if (timeSinceCheckin > CheckinFrequency)
{
var checkinTask = Checkin();
runningTasks.Add(checkinTask);
}
try
{
// The loop will 'tick' every second.
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
}
catch (TaskCanceledException)
{
// Break out of the long running task because the Task was cancelled externally
break;
}
counter++;
}
}
// Custom override of StopAsync. This is only triggered when the application
// GRACEFULLY shuts down. If it is not graceful, this code will not execute. Neither will the code for StopAsync in the base method.
public override async Task StopAsync(CancellationToken cancellationToken)
{
_logger.Info($"HostedService Gracefully Shutting down");
// Perform base StopAsync
await base.StopAsync(cancellationToken);
}
// Creates a task that performs a checkin, and returns the running task
private Task Checkin()
{
return Task.Run(async () =>
{
// await DoTheThingThatWillCheckin();
});
}
}
注意,您还可以覆盖StopAsync()方法来做一些日志记录,以及关闭事件所需的其他任何事情。在StopAsync中尽量避免临界逻辑,因为它不能保证被调用。
我有一个包含许多服务的服务框架,我也可以在web面板中看到每个服务的状态。所以,在我的解决方案中:
在StartAsync
方法中,我初始化并启动所有作业,系统等待作业完成,完成后返回Task.CompletedTask
在StopAsync
中,我尝试停止所有作业并确保它们已成功停止,然后返回Task.CompletedTask