加入收藏 | 设为首页 | 会员中心 | 我要投稿 北几岛 (https://www.beijidao.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

ASP.NET Core 3.x启动时运行异步任务(二)

发布时间:2021-05-21 07:53:48 所属栏目:大数据 来源: https://www.jb51.cc
导读:这一篇是接着前一篇在写的。如果没有看过前一篇文章,建议先去看一下前一篇,这儿是传送门 ? 一、前言 前一篇文章,我们从应用启动时异步运行任务开始,说到了必要性,也说到了几种解决方法,及各自的优缺点。最后,还提出了一个比较合理的解决方法:通过在

微信公众号:老王Plus

扫描二维码,关注个人公众号,可以第一时间得到最新的个人文章和内容推送

本文版权归作者所有,转载请保留此声明和原文链接

(编辑:北几岛)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

这一篇是接着前一篇在写的。如果没有看过前一篇文章,建议先去看一下前一篇,这儿是传送门

?

一、前言

前一篇文章,我们从应用启动时异步运行任务开始,说到了必要性,也说到了几种解决方法,及各自的优缺点。最后,还提出了一个比较合理的解决方法:通过在Program.cs里加入代码,来实现IWebHost启动前运行异步任务。

实现的代码再贴一下:

public?class?Program
{

????public?static?async?Task?Main(string[]?args)
????
{
????????IWebHost?webHost?=?CreateWebHostBuilder(args).Build();

????????using?(var?scope?=?webHost.Services.CreateScope())
????????{
????????????var?myDbContext?=?scope.ServiceProvider.GetrequiredService<MyDbContext>();

????????????await?myDbContext.Database.MigrateAsync();
????????}

????????await?webHost.RunAsync();
????}

????static?IWebHostBuilder?CreateWebHostBuilderstring[]?args)?=>
????????WebHost.CreateDefaultBuilder(args)
????????????.UseStartup<Startup>();
}

这个方法是有效的。但是,也会有一点不足。

从.Net Core的最简规则来说,我们不应该在Program.cs中加入其它代码。当然,我们可以把这部分代码转到一个外部类中,但最后也必须手动加入到Program.cs中。尤其是在多个应用中,使用相同的模式时,这种方式会很麻烦。

????为防止非授权转发,这儿给出本文的原文链接:https://www.cnblogs.com/tiger-wang/p/13714679.html

也许,我们可以采用向DI容器中注入启动任务?

二、向DI容器中注入启动任务

这种方式,是基于IStartupFilterIHostedService两个接口,通过这两个接口可以向依赖注入容器中注册类。

?

首先,我们为启动任务创建一个简单接口:

public?interface?IStartupTask
{
????Task?ExecuteAsync(CancellationToken?cancellationToken?=?default);
}

再建一个扩展方法,用来向DI容器注册启动任务:

static?ServiceCollectionExtensions
{

????static?IServiceCollection?AddStartupTask<T>(this?IServiceCollection?services)
????????where?T?:?class,?IStartupTask
????????=>?services.AddTransient<IStartupTask,?T>();

}

最后,再建一个扩展方法,在应用启动时,查找所有已注册的IStartupTask,按顺序执行他们,然后启动IWebHost

StartupTaskWebHostExtensions
{
????RunWithTasksAsync(this?IHost?webHost,?CancellationToken?cancellationToken?=?default)
????
{
????????var?startupTasks?=?webHost.Services.GetServices<IStartupTask>();

????????foreach?(var?startupTask?in?startupTasks)
????????{
????????????await?startupTask.ExecuteAsync(cancellationToken);
????????}

????????await?webHost.RunAsync(cancellationToken);
????}
}

这样就齐活了。

?

还是用一个例子来看看这个方式的具体应用。

三、示例 - 数据迁移

实现IStartupTask其实和实现IStartupFilter很相似,可以从DI容器中注入。如果需要考虑作用域,还可以注入IServiceProvider,并手动创建作用域。

?

例子中,数据迁移类可以写成这样:

MigratorStartupFilter:?IStartupTask
{
????private?readonly?IServiceProvider?_serviceProvider;
????public?MigratorStartupFilter(IServiceProvider?serviceProvider)
????
{
????????_serviceProvider?=?serviceProvider;
????}

????public?async?Task?default)
????{
????????using(var?scope?=?_seviceProvider.CreateScope())
????????{
????????????var?myDbContext?=?scope.ServiceProvider.GetrequiredService<MyDbContext>();
????????????await?myDbContext.Database.MigrateAsync();
????????}
????}
}

下面,把任务注入到ConfigureServices()中:

void?ConfigureServices(IServiceCollection?services)
{
????services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

????services.AddStartupTask<MigrationStartupTask>();
}

最后,用上一节中的扩展方法RunWithTasksAsync()来替代Program.cs中的Run():

string[]?args)
????{
????????//?await?CreateWebHostBuilder(args).Build().RunAsync();
????????await?(args).Build().();
????}

????string[]?args)?=>
????????WebHost.CreateDefaultBuilder(args)
????????????.UseStartup<Startup>();
}

?

从功能上来说,跟上一篇的代码区别不大,但这样的写法,又多了一些优点:

  1. 任务代码放到了Program.cs之外。这符合微软的建议,也更容易理解;
  2. 任务放到了DI容器中,这样更容易添加额外的任务;
  3. 如果没有额外任务,这个代码和标准的Run()一样,所以这个代码可以独立成一个模板。

简单来说,使用RunWithTasksAsync()后,可以轻松地向DI容器添加额外的任务,而不需要任何其它的更改。

?

满意了吗?好像感觉还差一点点…

四、不够完美的地方

如果要照着完美去做,好像还差一点点。

这个一点点是在于:任务现在运行在IConfiguration和DI容器配置完成后,IStartupFilters运行和中间件管道配置完成之前。换句话说,如果任务需要依赖于IStartupFilters,那这个方案行不通。

在大多数情况下,这没什么问题。以我自己的经验来看,好像没有什么功能需要依赖于IStartupFilters。但作为一个框架类的代码,需要考虑这种情况发生的可能性。

以目前的方案来说,好像还没办法解决。

应用启动时,当调用WebHost.Run()时,是内部调用WebHost。看一下StartAsync()的简化代码:

virtual?async?Task?StartAsyncdefault)
{
????_logger?=?_applicationServices.GetrequiredService<ILogger<WebHost>>();

????var?application?=?BuildApplication();

????_applicationLifetime?=?_applicationServices.GetrequiredService<IApplicationLifetime>()?as?ApplicationLifetime;
????_hostedServiceExecutor?=?_applicationServices.GetrequiredService<HostedServiceExecutor>();
????var?diagnosticSource?=?_applicationServices.GetrequiredService<DiagnosticListener>();
????var?httpContextFactory?=?_applicationServices.GetrequiredService<IHttpContextFactory>();
????var?hostingApp?=?new?HostingApplication(application,?_logger,?diagnosticSource,?httpContextFactory);

????await?Server.StartAsync(hostingApp,?cancellationToken).ConfigureAwait(false);

????_applicationLifetime?.NotifyStarted();

????await?_hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false);
}

如果我们希望任务是加在BuildApplication()调用和Server.StartAsync()的调用之间,该怎么办?

这段代码能给出答案:我们需要装饰IServer。 ¨K16K 首先,我们替换IServer的实现: ¨G8G 在这段代码中,我们拦截StartAsync()调用并注入任务,然后回到内置处理。 下面是对应的扩展代码: ¨G9G 这个扩展代码做了两件事:在DI容器中注册了IStartupTask,并装饰了之前注册的IServer实例。装饰方法Decorate()我略过了,有兴趣的可以去了解一下 - 装饰模式。 Program.cs的代码和第三节的代码相同,略过。 &emsp; 我们终于做到了在应用程序完全构建完成后去执行我们的任务,包括IStartupFilters`和中间件管道。

现在的流程,类似于下面这个微软官方的图:

(全文完)

?

?


?

    推荐文章
      热点阅读