我刚刚发现了IHostedService
和.NET Core 2.1 BackgroundService
类。我觉得这个想法太棒了。 Documentation
我发现的所有示例都用于长时间运行的任务(直到应用程序死亡)。 但我需要它很短的时间。这是正确的做法吗?
例如:
我想在应用程序启动后执行一些查询(大约需要10秒钟)。并且只有在开发模式下。我不想延迟应用程序启动,因此IHostedService
似乎很好。我不能使用Task.Factory.StartNew
,因为我需要依赖注入。
目前我这样做:
public class UpdateTranslatesBackgroundService: BackgroundService
{
private readonly MyService _service;
public UpdateTranslatesBackgroundService(MyService service)
{
//MService injects DbContext, IConfiguration, IMemoryCache, ...
this._service = service;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await ...
}
}
启动:
public static IServiceProvider Build(IServiceCollection services, ...)
{
//.....
if (hostingEnvironment.IsDevelopment())
services.AddSingleton<IHostedService, UpdateTranslatesBackgroundService>();
//.....
}
但这似乎有点矫枉过正。是吗?注册singleton(表示类在应用程序存在时存在)。我不需要这个。只需创建类,运行方法,配置类。全部都是后台任务。
答案 0 :(得分:4)
我觉得这里有一个以上的问题。 首先让我指出你可能知道的异步!=多线程。 所以BackgroundService不会让你成为应用程序&#34;多线程&#34;它可以在一个线程内运行,没有任何问题。如果你在该线程上进行阻塞操作,它仍将阻止启动。让我们在课堂上说,你以一种类似于
的非真实异步方式实现所有sql查询public class StopStartupService : BackgroundService
{
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
System.Threading.Thread.Sleep(1000);
return Task.CompletedTask;
}
}
这仍将阻止启动。
所以还有另外一个问题。
您应该如何运行后台作业?
对于简单的情况Task.Run
(如果你不确定如何配置它,尽量避免使用Task.Factory.StartNew)应该做的工作,但这并不是说这是最好还是好这样做的方式。有一堆开源库可以为你做这件事,看看他们提供的东西可能会很好。您可能没有注意到很多问题,如果您只使用Task.Run
,可能会产生令人沮丧的错误
我能看到的第二个问题是。
我应该点火并忘记c#?
对我而言,这是肯定的否定(但XAML人可能不同意)。无论你做什么,你都需要跟踪你正在做的事情何时完成。在您的情况下,如果有人在查询完成之前停止应用程序,您可能希望在数据库中进行回滚。但是,当您可以开始使用查询提供的数据时,您可能想知道更多。所以BackgroundService
可以帮助您简化执行,但很难跟踪完成情况。
你应该使用单身吗?
正如您已经提到的,使用单身人士可能是一件危险的事情,特别是如果您没有正确地清理事物,但更多的是您正在使用的服务的上下文对于对象的生命周期是相同的。因此,如果存在问题,这一切都取决于您对服务的实施。
我做这样的事情来做你想做的事。
public interface IStartupJob
{
Task ExecuteAsync(CancellationToken stoppingToken);
}
public class DBJob : IStartupJob
{
public Task ExecuteAsync(CancellationToken stoppingToken)
{
return Task.Run(() => System.Threading.Thread.Sleep(10000));
}
}
public class StartupJobService<TJob> : IHostedService, IDisposable where TJob: class,IStartupJob
{
//This ensures a single start of the task this is important on a singletone
private readonly Lazy<Task> _executingTask;
private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource();
public StartupJobService(Func<TJob> factory)
{
//In order for the transient item to be in memory as long as it is needed not to be in memory for the lifetime of the singleton I use a simple factory
_executingTask = new Lazy<Task>(() => factory().ExecuteAsync(_stoppingCts.Token));
}
//You can use this to tell if the job is done
public virtual Task Done => _executingTask.IsValueCreated ? _executingTask.Value : throw new Exception("BackgroundService not started");
public virtual Task StartAsync(CancellationToken cancellationToken)
{
if (_executingTask.Value.IsCompleted)
{
return _executingTask.Value;
}
return Task.CompletedTask;
}
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
if (_executingTask == null)
{
return;
}
try
{
_stoppingCts.Cancel();
}
finally
{
await Task.WhenAny(_executingTask.Value, Task.Delay(Timeout.Infinite,
cancellationToken));
}
}
public virtual void Dispose()
{
_stoppingCts.Cancel();
}
public static void AddService(IServiceCollection services)
{
//Helper to register the job
services.AddTransient<TJob, TJob>();
services.AddSingleton<Func<TJob>>(cont =>
{
return () => cont.GetService<TJob>();
});
services.AddSingleton<IHostedService, StartupJobService<TJob>>();
}
}
答案 1 :(得分:4)
这项工作无需做任何魔术。
简单地:
ConfigureServices
中运行的服务Configure
中解析所需的实例并运行它。Task.Run
。您必须注册该实例,否则依赖项注入将不起作用。那是不可避免的。如果您需要DI,则必须这样做。
除此之外,按照您的要求进行操作很简单,像这样:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddTransient<MyTasks>(); // <--- This
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
// Blocking
app.ApplicationServices.GetRequiredService<MyTasks>().Execute();
// Non-blocking
Task.Run(() => { app.ApplicationServices.GetRequiredService<MyTasks>().Execute(); });
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc();
}
}
public class MyTasks
{
private readonly ILogger _logger;
public MyTasks(ILogger<MyTasks> logger)
{
_logger = logger;
}
public void Execute()
{
_logger.LogInformation("Hello World");
}
}
BackgroundService
专门存在用于长时间运行的进程;如果是一次,请不要使用它。
答案 2 :(得分:1)
有一个名为Communist.StartupTasks的库可以处理这个确切的场景。它可以在Nuget上找到。
它专门用于在.NET Core App中启动应用程序期间运行任务。它完全支持依赖注入。
请注意,它会按顺序执行任务,并会阻止所有任务完成(即,在启动任务完成之前,您的应用不接受请求)。