后台任务

内置有后台任务基类,可快速实现后台任务,支持定时任务。

实现后台任务

BackgroundTaskopen in new window 为内置的后台任务抽象基类,通过从该类继承来实现具体的后台任务服务。 其中 DoWorkAsyncopen in new window 是任务具体执行方法,在继承类必须重写,其它属性/方法则按需重写。

// 实现一个后台任务服务:
[UseDI(typeof(MyBackgroundService))]
public class MyBackgroundService : BackgroundTask
{
    // DoWorkAsync() 将被无限次循环执行
    protected override async Task<IServiceResult> DoWorkAsync(CancellationToken stoppingToken)
    {
        Console.WriteLine("MyBackgroundService.DoWorkAsync()");
        await Task.Delay(TimeSpan.FromSeconds(1));
        return ServiceResult.Ok();
    }
}

// 实现一个定时后台任务服务:
[UseDI(typeof(MyScheduledService))]
public class MyScheduledService : BackgroundTask
{
    // 周期为 5 秒
    public override int TimerTaskPeriodInMilliseconds => 5 * 1000;

    // 5 秒执行一次 DoWorkAsync()
    protected override async Task<IServiceResult> DoWorkAsync(CancellationToken stoppingToken)
    {
        WorkMessage = $"我的定时任务第 {WorkExecutingRound} 次执行";
        Console.WriteLine(WorkMessage);
        await Task.Delay(TimeSpan.FromSeconds(1));
        return ServiceResult.Ok();
    }
}

注册并随应用启动

后台任务服务要以 Singleton 单例模式注入到系统中,在 Program.cs 里加入:

// 注册服务:
// (如果利用了 QuickAdmin.Net 提供的批量注册方法,可不用这样一个个注册,参见开发指南章节)
builder.Services.AddSingleton(typeof(MyBackgroundService));
builder.Services.AddSingleton(typeof(MyScheduledService));

// 让后台任务服务随应用一起启动起来:
// 以下只添加了 MyBackgroundService,因此 MyBackgroundService 会随应用立即启动起来,而 MyScheduledService 则为停止状态,
// 可去内置的后台任务管理页面,或自行在某处通过代码手工启动 MyScheduledService
builder.Services.AddHostedService<MyBackgroundService>(provider => provider.GetService<MyBackgroundService>());

注意在定义服务时加上 UseDIopen in new window 特性并指定服务类型为自己,就像前边示例那样, 以确保当使用 QuickAdmin.Net 提供的批量注册方法时能够正确注入到系统中。

后台任务管理页面

系统内置的后台任务管理页面为:~/QAdmin/Setup/BkTasks。Demo 里后台任务管理菜单项位于 系统管理->系统配置 菜单下。
可在该页面里查看已加载任务信息,并进行启动/暂停/继续/停止等操作。
BkTasks1
点击行首的加号或双击行将展开显示更多任务信息:
BkTasks1

自动启动并保持运行

你可能需要确保后台任务能够自动运行起来(比如服务器重启后)。默认情况下 WebApp 在收到请求前是不会自动运行起来的,其中的后台任务自然也就不会运行。
另外你可能也需要后台任务能够一直保持运行状态。
要达到此目的,请参考以下设置。

后台任务代码

  • 确保 DoWorkAsync() 里捕获并处理了所有异常:
[UseDI(typeof(MyBackgroundService))]
public class MyBackgroundService : BackgroundTask
{
    protected override async Task<IServiceResult> DoWorkAsync(CancellationToken stoppingToken)
    {
        try
        {
            ...
        }
        catch
        {
        }
    }
}
  • 让后台任务服务随应用一起启动起来,在 Program.cs 里加入:
builder.Services.AddHostedService<MyBackgroundService>(provider => provider.GetService<MyBackgroundService>());

宿主配置(IIS)

若是将 ASP.NET Core WebApp 发布到了 IIS,默认情况下 WebApp 并不会随 WWW 服务的启动而自动运行起来,并且由于应用程序池的默认回收机制, 导致 WebApp 会被周期性地关闭,其中的后台任务自然无法一直保持运行。
因此配置的目标便是让 WebApp 能随 WWW 服务的启动而自动运行起来,并且防止 WebApp 被周期性地关闭。具体操作参考以下步骤。

  1. 确保 WWW 服务的启动类型是 "自动"。默认是 "自动",除非你自行修改过。
  2. 安装 "应用程序初始化(Application Initialization)" 模块。安装该模块的目的是让 WebApp 在收到外部请求之前也能运行起来。
    在 Windows Server 里(Server 2012+),打开服务器管理器,通过 添加角色和功能 → Web 服务器 (IIS) → Web 服务器 → 应用程序开发 → 应用程序初始化 安装:
    WindowsServerApplicationInitialization
    在 Win10/Win11 里,通过 控制面板 → 程序 → 启用或关闭 Windows 功能 → Internet Information Services → 万维网服务 → 应用程序开发功能 → 应用程序初始化 安装:
    Win10/Win11ApplicationInitialization
  3. 配置应用程序池一直运行且禁止自动回收。
    打开你的 WebApp 使用的应用程序池的高级配置,按下图所示配置:
    AppPool
  • "启动模式" 设为 "AlwaysRunning",指示应用程序池一直运行
  • "固定时间间隔" 默认为 1740 分钟,即应用程序池始终在启动后大约 29 小时强制重启,无论当前是否有用户在访问系统,将其改为 0 表示禁用定期回收
  • "闲置超时" 默认为 20 分钟,即若 20 分钟内一直无请求,也会被回收,设为 0 则禁用空闲回收
  1. 配置 WebApp 启用预加载功能。
    打开你的 WebApp 的高级配置,将 "预加载已启用" 置为 "True":
    WebApp
  2. 修改你的 WebApp 下的 web.config,确保使用进程内托管(发布应用时,已默认为 inprocess 了,除非你自行修改过),并配置 applicationInitialization:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <location path="." inheritInChildApplications="false">
    <system.webServer>
      <handlers>
        <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
      </handlers>
      <!-- 确保 hostingModel 为 "inprocess":-->
      <aspNetCore processPath="dotnet" arguments=".\QAdminSampleApp.Web.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" />
      <!-- Application Initialization 模块配置: -->
      <applicationInitialization 
          doAppInitAfterRestart="true"
          skipManagedModules="false" 
          remapManagedRequestsTo="/" >
        <add initializationPage="/" />
      </applicationInitialization>      
    </system.webServer>
  </location>
</configuration>








 

 
 
 
 
 
 



  • doAppInitAfterRestart 设为 "true",使应用重启时自动启动初始化过程
  • skipManagedModules 设为 "false",因为需要加载托管模块
  • remapManagedRequestsTo 指示在初始化期间要将请求重新映射到的页,比如一个用来给用户提示应用正在启动中的静态页面
  • initializationPage 指示初始化时自动请求的应用的 Url

使用进程内托管("inprocess"),WebApp 将直接在 w3wp.exe 进程中运行,更易与 IIS 的预加载机制集成。
加入 applicationInitialization 配置,将使 WebApp 即使没有收到外部请求也能自动运行起来。有关 Application Initialization 模块的更多信息请参阅官方文档open in new window

验证效果

可在后台任务启动事件里去添加一条日志:

[UseDI(typeof(MyScheduledService))]
public class MyScheduledService : BackgroundTask
{
    // 周期为 5 秒
    public override int TimerTaskPeriodInMilliseconds => 5 * 1000;

    // 5 秒执行一次 DoWorkAsync()
    protected override async Task<IServiceResult> DoWorkAsync(CancellationToken stoppingToken)
    {
        WorkMessage = $"我的定时任务第 {WorkExecutingRound} 次执行";
        Console.WriteLine(WorkMessage);
        await Task.Delay(TimeSpan.FromSeconds(1));
        return ServiceResult.Ok();
    }

    protected override async Task<IServiceResult> OnTaskStartingAsync(CancellationToken stoppingToken)
    {
        await AddExceptionLogAsync("我正在启动...");
        return await base.OnTaskStartingAsync(stoppingToken);
    }
}















 
 
 
 
 

编译、发布应用并将其部署到 IIS 下,按照本节所述进行配置,然后重启一下 WWW 服务并记住启动的时间,此时不要对应用发起任何请求(不要浏览任何页面或请求任何 API)
然后过一会再用超级管理员登录系统,看看后台任务的启动时间是否基本就是 WWW 服务启动时间,在系统异常日志里也会有一条该时间点的记录。