这是一个大的题目,需要用几篇文章来说清楚。这是第一篇。

一、前言

在我们的项目中,有时候我们需要在应用程序启动前执行一些一次性的逻辑。比方说:验证配置的正确性、填充缓存、或者运行数据库清理/迁移等。

如何合理、有效、优雅地完成这个任务,是这个文章讨论的主要内容。

要实现这样一个功能,其实我们有几个选择:

  1. 使用IStartupFilter运行同步任务。这是一个内置的解决方案,可以通过一些设置和技巧来运行异步任务;

  2. 使用IStartupFilterIApplicationLifetime事件来运行异步任务,这是一个可选的方案,但有不足,我们会在后面讲;

  3. 使用IHostedService,在不阻塞应用启动的情况下,运行一些一次性的任务;(关于这个内容,我在前一篇文章ASP.NET Core 3.x控制IHostedService启动顺序浅探中有涉及到一部分内容)

  4. Program.cs中运行异步任务。在大多数情况下,从代码的复杂度到效率上,这都是一个比较好的选择。

先提个问题:为什么要在应用启动时运行任务?

二、为什么要在应用启动时运行任务?

在应用启动并开始请求服务之前,很多时候需要运行各种初始化工作。

一个ASP.NET应用启动时,需要完成很多事,例如:

  • 确定当前的宿主环境

  • 加载appsetting.json配置和环境变量

  • 配置并创建依赖注入的容器

  • 配置中间件管道

这是应用启动时要完成的引导内容。

在完成这些内容,运行WebHost并开始监听请求之前,还会有一些一次性任务需要启动,例如:

  • 检查强类型配置的有效性

  • 填充或恢复缓存

  • 数据库清理/迁移(通常来说这不是个好主意,但很多时候没有别的办法)

当然,有些任务也不是一定要在开始监听请求之前运行,这要看具体的运行任务的架构。一般来说,如果缓存处理的完善,是不需要提前启动的。当然,清理/迁移数据库,是必须放在服务启动之前。

在微软官网上,有一个例子是数据保护子系统,用于即时加密(cookie、防伪令牌等),这个就必须在应用监听请求之前完成初始化并加载,这个例子使用了IStartupFilter

三、使用IStartupFilter运行同步任务

IStartupFilters作为配置中间件管道的一部分,通常在Startup.Configure()中运行。它允许我们定制应用的中间件管道,处理我们希望进行的所有任务。

看一个简单的例子:

public class AutoRequestServicesStartupFilter : IStartupFilter
{public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next){return builder =>{builder.UseMiddleware<RequestServicesContainerMiddleware>();next(builder);};}
}

IStartupFilter提供了一种可能,在依赖注入容器配置完成之后、应用程序启动之前运行一些代码。因此,我们可以在IStartupFilters中直接使用依赖注入。这表示我们可以运行有关系统的任何代码。在前边提到的微软官网的例子中,就是创建了一个基于IStartupFiltersDataProtectionStartupFilter来初始化数据保护子系统。

此外,IStartupFilter允许我们通过向依赖注入容器注册服务来增加要执行的任务。这是一个很有用的特性,表示我们可以注册一个在应用启动时运行的任务,而不需要显式的调用。

但是,这儿有个问题。IStartupFilters通常运行的是同步的任务。看一下上面的代码,Configure()方法不返回任务。当然,我们硬要使用异步也是可以的,但一般来说,这不算个好主意。原因我后面会写。

写到这儿,如果对ASP.NET Core架构熟悉,就会引出另一个问题:为什么不用健康检查来确认一次性任务的执行结果?

四、为什么不用健康检查?

运行健康检查,是ASP.NET Core 2.2新引入的一个特性,允许查询通过API(HTTP Endpoint)公开的应用的健康状况。当应用部署在Kubernetes,或反向代理HAProxyNginx后面时,可以提供给代理用来检测应用是否准备好开始提供服务。

我们可以使用健康检查来确保应用所有必需的一次性任务完成之前不会开始监听服务。

但是,这种方式会有一点问题。

WebHostKestrel本身会在一次性任务执行前启动。当然,这时他们还不会接收和处理服务请求,但仍然引出了一些问题:

首先是增加了代码的复杂性。除了一次性任务的代码外,还要增加健康检查来测试任务是否完成,并同步和保持任务的状态;其次,如果任务失败了,应用程序的健康检查将会让应用后续的任务无法继续执行。合理的流程是:应用应该立即失败返回。

这儿主要的原因是:健康检查没有定义如何实际运行任务,而只是定义了任务是否成功完成。相对来说,这种状态机制比较单一,在一些简单的任务中可能适用,但不能全面覆盖一次性任务的全部场景。

五、运行异步任务

前边写了一些不太完美的方法。

现在,我们开始进入运行异步方法的一些步骤。当然,运行异步也会有几种方式,适用性上会有一定的区别。

方式1:使用IStartupFilter

前边说过,使用IStartupFilter时,执行的是同步任务。所以,我们可以通过GetAwater().GetResult()来调用异步。

我们拿数据迁移来举个例子。在EF Core中,通过myDBContext.database.migrateasync()在运行时进行数据库迁移。其中,myDBContext是应用程序中DBContext的一个实例。

public class MigratorStartupFilter: IStartupFilter
{private readonly IServiceProvider _serviceProvider;public MigratorStartupFilter(IServiceProvider serviceProvider){_serviceProvider = serviceProvider;}public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next){using(var scope = _seviceProvider.CreateScope()){var myDbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();myDbContext.Database.MigrateAsync().GetAwaiter().GetResult();}return next;}
}

通常,GetAwaiter().GetResult()要注意避免死锁的问题。但这儿可能不需要,因为这个代码只在启动时运行,这时候还没有需要处理的请求,所以不太会死锁。

只能说,这样可以用。不过习惯上我会避免这么做。

方式2:使用IApplicationLifetime事件

这是另一个选择。可以通过IApplicationLifetime事件,在应用启动和关闭时接收通知,处理任务。

但这个方式也有局限性。

首先,IApplicationLifetime使用cancellationtoken来注册回调,也就是说,这又是一个同步方式,又需要使用GetAwaiter().GetResult()来调用异步。

其次,ApplicationStarted事件是在WebHost启动之后才会触发,因此异步任务也是在应用开始监听请求后才运行。

方式3:使用IHostedService

IHostedService可以让ASP.NET Core应用在后台执行长时间的任务。

一般来说,IHostedService用在周期性任务、消息传递等任务上,但实际上它并不限于运行这些任务。在ASP.NET Core 3.x上,WebHost本身也是建立在IHostedService上的。

而且,IHostedService本身就是异步的,它提供了StartAsyncStopAsync

这种方式下,我们的代码会是这样:

public class MigratorHostedService: IHostedService
{private readonly IServiceProvider _serviceProvider;public MigratorStartupFilter(IServiceProvider serviceProvider){_serviceProvider = serviceProvider;}public async Task StartAsync(CancellationToken cancellationToken){using(var scope = _seviceProvider.CreateScope()){var myDbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();await myDbContext.Database.MigrateAsync();}}public Task StopAsync(CancellationToken cancellationToken){return Task.CompletedTask;}
}

根据例子可以看出,IHostedService可以直接运行异步任务。

但是,IHostedService也有局限性。从微软官网的说明来看,IHostedService实现期望StartAsync能相对较快的返回。对于后台任务,倾向于异步启动,但主要任务在启动后执行。

在上面这个例子中,数据迁移本身不是问题,但这个长时任务会阻止其它`IHostedService启动和运行。而且,应用会在IHostedService完成数据迁移前开始监听并响应请求,这是一个严重的问题。

方式4:在Program.cs中运行

上面三个方式,都可以解决启动时运行异步任务的问题,但都不够完美,要么要求使用同步(异步转同步可以用,但有隐藏问题),要么不能阻止应用启动,会造成应用启动完成后,可能异步任务还未完成的情况。

我在前边的博文中写到过关于Program.cs中运行IHostedService的方式。具体可以去看ASP.NET Core 3.x控制IHostedService启动顺序浅探

看一下Program.cs的默认代码:

public class Program
{public static void Main(string[] args){CreateWebHostBuilder(args).Build().Run();}public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>WebHost.CreateDefaultBuilder(args).UseStartup<Startup>();
}

Build()创建WebHost之后,调用Run()之前,完全可以加入我们需要的代码。同时,C# 7.1后主函数可以改为异步运行。

因此,我们可以在这儿做些文章:

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();}public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>WebHost.CreateDefaultBuilder(args).UseStartup<Startup>();
}

这个方案的好处是:

  • 这是真正的异步;

  • 任务完成后,应用程序才可以监听并接受请求;

  • 此时已经构建了依赖注入容器,所以可以创建服务;

当然,同样也会有不足:这儿只是构建了DI容器,但并没有建立管道(管道在Run()RunAsync()后才建立,然后是IStartupFilters执行,再然后是应用程序启动)。因此异步任务不能使用管道、IStartupFilters中的配置。不过,这种需求的情况很少。

六、总结

这个部分牵扯到的框架内容比较多。

我们从应用启动时异步运行任务开始,说到了必要性,也说到了几种解决方法,及各自的优缺点。

下一篇文章,我会用一些具体的例子,来说清楚这个方式的具体使用,敬请关注。

(未完待续)

ASP.NET Core 3.x启动时运行异步任务(一)相关推荐

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

    这一篇是接着前一篇在写的.如果没有看过前一篇文章,建议先去看一下前一篇,这儿是传送门   一.前言 前一篇文章,我们从应用启动时异步运行任务开始,说到了必要性,也说到了几种解决方法,及各自的优缺点.最 ...

  2. 如何在ASP.NET Core程序启动时运行异步任务(3)

    原文:Running async tasks on app startup in ASP.NET Core (Part 3) 作者:Andrew Lock 译者:Lamond Lu 之前我写了两篇有关 ...

  3. 如何在ASP.NET Core程序启动时运行异步任务(2)

    原文:Running async tasks on app startup in ASP.NET Core (Part 2) 作者:Andrew Lock 译者:Lamond Lu 在我的上一篇博客中 ...

  4. 如何在ASP.NET Core程序启动时运行异步任务(1)

    原文:Running async tasks on app startup in ASP.NET Core (Part 1) 作者:Andrew Lock 译者:Lamond Lu 背景 当我们做项目 ...

  5. windows server 2008 r2 托管 asp.net core 程序无法启动

    windows server 2008 r2 托管 asp.net core 程序无法启动时,需安装以下更新: Update for Windows Server 2008 R2 x64 Editio ...

  6. ASP.NET Core 2.1 使用Docker运行

    1.新建一个 ASP.NET Core 2.1 项目 然后运行一下项目,确保我们刚刚建立的项目可以正常运行. 2.编写 Dockerfile 新建一个文本文件,命名为 Dockerfile FROM ...

  7. 在Spring Boot启动时运行代码

    Spring Boot会自动为我们执行很多配置,但是迟早您将不得不做一些自定义工作. 在本文中,您将学习如何进入应用程序引导生命周期并在Spring Boot启动时执行代码 . 因此,让我们看看该框架 ...

  8. 在 Excel 启动时运行宏

    如果您希望每当启动 Microsoft Excel 时都自动执行某些操作,可以录制或编写一个每当打开工作簿时都将运行的宏.有两个办法可以做到这一点: 录制一个宏,然后用 Auto_Open 这一名称保 ...

  9. 计算机启动时运行ccleaner,CCleaner中设置表详解

    CCleaner是一款具备强大清理能力的系统优化和隐私保护软件.它可以清理文件夹.历史记录.注册表垃圾等,还能对电脑磁盘进行清理. 在大家使用CCleaner的过程中,是否想过更改CCleaner的设 ...

最新文章

  1. 打通Devops的Scrum敏捷工具
  2. codeforces 拼手速题2
  3. JDK 13 的最新垃圾回收器ZGC,你了解多少?
  4. delphi webbrowser 经常用法演示样例
  5. http响应协议分析
  6. 基于 Flink 的超大规模在线实时反欺诈系统的建设与实践
  7. centos卸载内核_CentOS 中内核模块的加载和卸载
  8. Python中正则表达式讲解
  9. mysql网络订餐系统截屏_在线订餐系统mysql字段
  10. 论文浅尝 - AAAI2020 | 迈向建立多语言义元知识库:用于 BabelNet Synsets 义元预测...
  11. jQuery.html()方法ie下不能设置html代码的问题
  12. Qt实现全屏下玫瑰花(含详细注释)
  13. Echarts 出现不明竖线解决方案
  14. visual studio 2015 rc cordova -hello world
  15. Atitit go语言 golang 艾提拉总结特性优缺点 目录 1. Go 语言最主要的特性: 1 2. 体积大概100M 1 3. 问题 1 3.1. 编译速度和异常控制怎么样 1 3.2.
  16. 高通GPS芯片WGR7640的驱动
  17. 联合(联合体,共用体)详解
  18. Tumblr创始人David Karp:文艺技术男的成功之路
  19. linux 无线投屏windows,拼接屏加入无线投屏功能,新功能智慧体验
  20. 【隧道篇 / PPTPL2TP】(5.2) ❀ 01. PPTP L2TP 连接 ❀ FortiGate 防火墙

热门文章

  1. el表达式 if 和 if else 的写法
  2. 操作系统,,,也考完了【流坑】
  3. python numpy矩阵索引_python – Numpy中的矩阵索引
  4. ipad和iphone切图_如何在iPhone和iPad上密码保护照片
  5. discord linux_如何在Discord中应用文本格式
  6. dvd vlc 复制_如何使用VLC翻录DVD
  7. gfi截图_GFI Backup Home Edition是Windows的免费数据备份实用程序
  8. c语言如何创建虚拟串口,模拟串口的C语言源程序代码
  9. mysql索引三个字段查询两个字段_mysql中关于关联索引的问题——对a,b,c三个字段建立联合索引,那么查询时使用其中的2个作为查询条件,是否还会走索引?...
  10. 『中级篇』Dockerfile详解(17)