Mak*_*kla 9 c# .net-core asp.net-core
我刚刚发现了IHostedService.NET Core 2.1 BackgroundService类.我觉得这个想法太棒了.文档.
我发现的所有示例都用于长时间运行的任务(直到应用程序死亡).但我需要它很短的时间.这样做的正确方法是什么?
例如:
我想在应用程序启动后执行一些查询(大约需要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 ...
}
}
Run Code Online (Sandbox Code Playgroud)
启动:
public static IServiceProvider Build(IServiceCollection services, ...)
{
//.....
if (hostingEnvironment.IsDevelopment())
services.AddSingleton<IHostedService, UpdateTranslatesBackgroundService>();
//.....
}
Run Code Online (Sandbox Code Playgroud)
但这似乎有点矫枉过正.是吗?注册singleton(表示类在应用程序存在时存在).我不需要这个.只需创建类,运行方法,配置类.全部在后台任务.
好吧,我认为这里有不止一个问题。首先让我指出一些你可能知道的 async != multithreaded。所以 BackgroundService 不会让你的应用程序“多线程”,它可以毫无问题地在单线程内运行。如果你在那个线程上做阻塞操作,它仍然会阻塞启动。让我们说在类中,您以非真正的异步方式实现所有 sql 查询,类似于
public class StopStartupService : BackgroundService
{
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
System.Threading.Thread.Sleep(1000);
return Task.CompletedTask;
}
}
Run Code Online (Sandbox Code Playgroud)
这仍然会阻止启动。
所以还有一个问题。
您应该如何运行后台作业?
为此,在简单的情况下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>>();
}
}
Run Code Online (Sandbox Code Playgroud)
没有必要为这个工作做任何魔法。
简单地:
ConfigureServicesConfigure并运行它。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");
}
}
Run Code Online (Sandbox Code Playgroud)
BackgroundService专为长时间运行的进程而存在;如果是一次,请不要使用它。
| 归档时间: |
|
| 查看次数: |
6489 次 |
| 最近记录: |