[英]Short running background task in .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 ...
}
}
啟動:
public static IServiceProvider Build(IServiceCollection services, ...)
{
//.....
if (hostingEnvironment.IsDevelopment())
services.AddSingleton<IHostedService, UpdateTranslatesBackgroundService>();
//.....
}
但這似乎有點矯枉過正。 是嗎? 注冊singleton(表示類在應用程序存在時存在)。 我不需要這個。 只需創建類,運行方法,配置類。 全部在后台任務。
那么我認為這里有一個以上的問題。 首先讓我指出你可能知道的異步!=多線程。 所以BackgroundService不會讓你的應用程序“多線程”它可以在一個線程內運行而沒有任何問題。 如果你在該線程上進行阻塞操作,它仍將阻止啟動。 讓我們在課堂上說你用一種非真實的異步方式實現所有的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>>();
}
}
沒有必要為此工作做任何魔術。
只是:
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
專門用於長時間運行的進程; 如果是一次,請不要使用它。
有一個名為Communist.StartupTasks的庫可以處理這個確切的場景。 它可以在Nuget上找到。
它專門設計用於在.NET Core App中啟動應用程序期間運行任務。 它完全支持依賴注入。
請注意,它會按順序執行任務,並且會阻止所有任務完成(即,在啟動任務完成之前,您的應用程序將不接受請求)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.