Kon*_*ten 5 c# .net-core asp.net-core-hosted-services asp.net-core-5.0
尝试大致遵循MSDN,我在课堂上的范围服务之后添加了托管服务StartUp。
public void ConfigureServices(IServiceCollection services)
{
...
services.AddScoped<IUtilityService, UtilityService>();
services.AddHostedService<StartupService>();
...
}
Run Code Online (Sandbox Code Playgroud)
我是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();
}
}
Run Code Online (Sandbox Code Playgroud)
我读过很多文章和博客,但我无法理解方法末尾应该返回什么。它现在似乎有效,但我可以清楚地看到,我通过不使用异步调用并返回一个虚拟值来违反这个想法(甚至在停止时也没有!),所以我可以安全地得出结论,我做错了(尽管不是)显然,但我确信它将来会咬我的屁股)。
我应该在实现中返回什么以确保我“使用”而不是反对框架?
StartAsync 需要返回一个任务,该任务可能正在运行,也可能没有运行(但理想情况下它应该正在运行,这就是 HostedService 的要点 - 在应用程序的生命周期内运行的操作/任务,或者只是在一段较长的时间内运行比正常时间长)。
看起来您正在尝试使用 HostedService 执行额外的启动项,而不是仅仅尝试运行将持续到应用程序的整个生命周期的任务/操作。
如果是这种情况,您可以进行非常简单的设置。您想要从 StartAsync() 方法返回的是一个任务。当您返回 Task.CompletedTask 时,您表示工作已经完成并且没有代码执行 - 任务已完成。您想要返回的是正在执行任务对象内部运行的额外启动项的代码。asp.net 中 HostedService 的好处是任务运行多长时间并不重要(因为它意味着在应用程序的整个生命周期中运行任务)。
代码示例之前的一个重要注意事项 - 如果您在任务中使用 Scoped 服务,那么您需要使用 IServiceScopeFactory 生成一个范围,请在这篇 StackOverflow 帖子中阅读相关内容
如果您重构服务方法以返回任务,您可以只返回该任务:
public Task StartAsync(CancellationToken)
{
IServiceScope scope = Provider.CreateScope();
IUtilityService service = scope.ServiceProvider
.GetRequiredService<IUtilityService>();
// If Seed returns a Task
return service.Seed();
}
Run Code Online (Sandbox Code Playgroud)
如果您有多个服务方法都返回一个任务,则可以返回一个正在等待所有任务完成的任务
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);
}
Run Code Online (Sandbox Code Playgroud)
如果您的启动任务执行大量 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();
}
}
Run Code Online (Sandbox Code Playgroud)
要使用取消令牌,下面的一些示例代码展示了如何通过在 Try/Catch 中捕获 TaskCanceledException 并强制退出运行循环来完成此操作。
然后我们继续处理将在整个应用程序生命周期中运行的任务。这是我用于所有 HostedService 实现的基类,这些实现被设计为在应用程序关闭之前永远不会停止运行。
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);
}
Run Code Online (Sandbox Code Playgroud)
在此基类中,您将看到,当调用 StartAsync 时,我们调用 ExecuteAsync() 方法,该方法返回一个包含 while 循环的任务 - 任务不会停止运行,直到我们的取消令牌被触发,或者应用程序优雅/强制地停止。
ExecuteAsync() 方法需要由继承该基类的任何类实现,该基类应该是所有 HostedService 的类。
下面是一个继承自该基类的 HostedService 实现示例,设计为每 30 秒签入一次。您会注意到 ExecuteAsync() 方法进入 while 循环并且永远不会退出 - 它每秒会“滴答”一次,您可以在此处调用其他方法,例如定期检查另一台服务器。此循环中的所有代码都在任务中返回到 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();
});
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,您还可以重写 StopAsync() 方法来执行一些日志记录以及关闭事件所需的任何其他操作。尽量避免 StopAsync 中的关键逻辑,因为它不能保证被调用。
| 归档时间: |
|
| 查看次数: |
2822 次 |
| 最近记录: |