在 .NET Core 5 的托管服务的 StartAsync 和 StopAsync 中 return 是什么?
What to return in StartAsync and StopAsync of a hosted service for .NET Core 5?
尝试大致遵循 MSDN,我在 StartUp
class.
中的范围服务之后添加了托管服务
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();
}
}
我已经阅读了很多文章和博客,但我无法理解在方法末尾应该 return 编辑什么。它现在似乎工作,但我可以清楚地看到我通过不使用异步调用和 returninig 一个虚拟(甚至不是停止!)来破坏这个想法,所以我可以安全地得出结论,我正在做它错了(虽然不是很明显,但我相信它会在未来咬我的屁股)。
我应该 return 在实施中做什么以确保我“使用”框架而不是反对框架?
我有一个包含许多服务的服务框架,我也可以在 Web 面板中看到每个服务的状态。所以,在我的解决方案中:
在StartAsync
方法中,我初始化并启动所有作业,所以系统等待作业完成,完成后,我return Task.CompletedTask
在 StopAsync
中,我尝试停止所有作业并确保它们已成功停止,然后 return Task.CompletedTask
StartAsync 需要 return 一个任务,它可能是也可能不是 运行ning(但理想情况下它应该是 运行ning,这就是 HostedService 的重点 - operation/task 运行 在应用程序的生命周期内,或者只是比正常情况下更长的一段时间)。
看起来您正在尝试使用 HostedService 执行额外的启动项,而不是仅仅尝试 运行 将持续应用程序整个生命周期的 task/operation。
如果是这种情况,您可以进行非常简单的设置。您想要从 StartAsync() 方法中 return 的东西是一个任务。当您 return a Task.CompletedTask 时,您是在说工作已经完成并且没有代码执行 - 任务已完成。您想要 return 的代码是在 Task 对象中执行额外启动项的 运行ning。 asp.net 中的 HostedService 的好处在于,任务 运行 持续多长时间并不重要(因为它意味着 运行 应用程序整个生命周期的任务)。
代码示例之前的一个重要说明 - 如果您在任务中使用 Scoped 服务,那么您需要使用 IServiceScopeFactory 生成一个范围,阅读相关内容
如果您将服务方法重构为 return 一项任务,您可以 return 这样:
public Task StartAsync(CancellationToken)
{
IServiceScope scope = Provider.CreateScope();
IUtilityService service = scope.ServiceProvider
.GetRequiredService<IUtilityService>();
// If Seed returns a Task
return service.Seed();
}
如果您有 多个 服务方法,所有 return 一个任务,您可以 return 一个正在等待所有任务完成的任务
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 绑定工作,只需 return a 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,然后强制退出我们的 运行ning 循环。
然后我们继续执行 运行 整个应用程序生命周期的任务。
这是我用于所有 HostedService 实现的基础 class,这些实现旨在在应用程序关闭之前永不停止 运行ning。
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);
}
在此 Base Class 中,您将看到调用 StartAsync 时,我们会调用我们的 ExecuteAsync() 方法,该方法 return 是一个包含 while 循环的任务 - 任务不会停止 运行ning 直到我们的取消令牌被触发,或者应用程序 gracefully/forcefully 停止。
ExecuteAsync() 方法需要由继承自该基础 class 的任何 class 实现,这应该是您的所有 HostedService 的。
这是一个 HostedService 实现示例,它继承自此 Base class,旨在每 30 秒签入一次。您会注意到 ExecuteAsync() 方法进入 while 循环并且永远不会退出 - 它每秒 'tick' 一次,这是您可以调用其他方法的地方,例如定期检查另一台服务器。此循环中的所有代码都在任务中 returned 到 StartAsync() 并 returned 到调用方。直到 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 中的关键逻辑,因为它不能保证被调用。
尝试大致遵循 MSDN,我在 StartUp
class.
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();
}
}
我已经阅读了很多文章和博客,但我无法理解在方法末尾应该 return 编辑什么。它现在似乎工作,但我可以清楚地看到我通过不使用异步调用和 returninig 一个虚拟(甚至不是停止!)来破坏这个想法,所以我可以安全地得出结论,我正在做它错了(虽然不是很明显,但我相信它会在未来咬我的屁股)。
我应该 return 在实施中做什么以确保我“使用”框架而不是反对框架?
我有一个包含许多服务的服务框架,我也可以在 Web 面板中看到每个服务的状态。所以,在我的解决方案中:
在StartAsync
方法中,我初始化并启动所有作业,所以系统等待作业完成,完成后,我return Task.CompletedTask
在 StopAsync
中,我尝试停止所有作业并确保它们已成功停止,然后 return Task.CompletedTask
StartAsync 需要 return 一个任务,它可能是也可能不是 运行ning(但理想情况下它应该是 运行ning,这就是 HostedService 的重点 - operation/task 运行 在应用程序的生命周期内,或者只是比正常情况下更长的一段时间)。
看起来您正在尝试使用 HostedService 执行额外的启动项,而不是仅仅尝试 运行 将持续应用程序整个生命周期的 task/operation。
如果是这种情况,您可以进行非常简单的设置。您想要从 StartAsync() 方法中 return 的东西是一个任务。当您 return a Task.CompletedTask 时,您是在说工作已经完成并且没有代码执行 - 任务已完成。您想要 return 的代码是在 Task 对象中执行额外启动项的 运行ning。 asp.net 中的 HostedService 的好处在于,任务 运行 持续多长时间并不重要(因为它意味着 运行 应用程序整个生命周期的任务)。
代码示例之前的一个重要说明 - 如果您在任务中使用 Scoped 服务,那么您需要使用 IServiceScopeFactory 生成一个范围,阅读相关内容
如果您将服务方法重构为 return 一项任务,您可以 return 这样:
public Task StartAsync(CancellationToken)
{
IServiceScope scope = Provider.CreateScope();
IUtilityService service = scope.ServiceProvider
.GetRequiredService<IUtilityService>();
// If Seed returns a Task
return service.Seed();
}
如果您有 多个 服务方法,所有 return 一个任务,您可以 return 一个正在等待所有任务完成的任务
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 绑定工作,只需 return a 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,然后强制退出我们的 运行ning 循环。
然后我们继续执行 运行 整个应用程序生命周期的任务。 这是我用于所有 HostedService 实现的基础 class,这些实现旨在在应用程序关闭之前永不停止 运行ning。
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);
}
在此 Base Class 中,您将看到调用 StartAsync 时,我们会调用我们的 ExecuteAsync() 方法,该方法 return 是一个包含 while 循环的任务 - 任务不会停止 运行ning 直到我们的取消令牌被触发,或者应用程序 gracefully/forcefully 停止。
ExecuteAsync() 方法需要由继承自该基础 class 的任何 class 实现,这应该是您的所有 HostedService 的。
这是一个 HostedService 实现示例,它继承自此 Base class,旨在每 30 秒签入一次。您会注意到 ExecuteAsync() 方法进入 while 循环并且永远不会退出 - 它每秒 'tick' 一次,这是您可以调用其他方法的地方,例如定期检查另一台服务器。此循环中的所有代码都在任务中 returned 到 StartAsync() 并 returned 到调用方。直到 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 中的关键逻辑,因为它不能保证被调用。