一. 本文说明

本文档主要是介绍Hangfire的项目集成与使用方式, 参考官方文档和网络文章, 实践目前只基于.Net Framework WebApi项目进行实践可用, 所以本文也主要以此项目为示例进行步骤说明, 后续可能会更新.Net Core 项目的嵌入和使用方法

官方文档(英文)

官方文档(翻译)

Hangfire官方Git仓库及官方说明

Hangfire.Console仓库及官方说明

Hangfire.Dashboard.Authorization仓库及官方说明

Hangfire.RecurringJobAdmin仓库及官方说明

Hangfire.HttpJob仓库及官方说明

Hangfire.HttpJob 官方Wiki (github.com)

二. 服务搭建及配置

(一). 宿主程序

HangFire可以寄宿在多种.Net应用中,例:ASP.NET Web应用程序、非ASP.NET Web应用程序,控制台应用程序或Windows服务中使用。

支持.Net Framework 和 .Net Core ( Hangfire V1.6 + ), Framework 需要依托 Owin 实现。

本文以.Net Framework WebApi 程序为例进行部署示例, 其他宿主程序使用方式可见官方示例项目:HangfireIO/Hangfire.Samples(github.com)

(二). 安装依赖包

//owin,运行根本
PM> Install-Package Microsoft.Owin

//hangfire 核心包
PM> Install-Package Hangfire
PM> Install-Package Hangfire.Core

//hangfire 用于面板信息输出
PM> Install-Package Hangfire.Console

//hangfire 用于面板登录验证授权
PM> Install-Package Hangfire.Dashboard.Authorization

//hangfire 用于添加Http请求类型的后台任务,Hangfire.HttpJob是负责调度的, HttpJob.Client是负责发起job的
PM> Install-Package Hangfire.HttpJob
PM> Install-Package Hangfire.HttpJob.Client

//hangfire 用于持久化存储到sqlserver
PM> Install-Package Hangfire.SqlServer

//hangfire 用于面板添加任务(原面板是只能查看任务执行情况)
PM> Install-Package Hangfire.RecurringJobAdmin

(三). 配置服务

主要有两种方式:一种是基于Owin的Startup.cs进行配置,第二种是基于

  1. 依托于Owin的Startup.cs文件,没有可以创建该文件

    using Hangfire;
    using Hangfire.Console;
    using Hangfire.Dashboard;
    using Hangfire.Heartbeat;
    using Hangfire.HttpJob;
    using Hangfire.MemoryStorage;
    using Hangfire.RecurringJobAdmin;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.Owin;
    using Owin;
    using System;
    using System.Configuration;
    using System.Diagnostics;
    using System.IO;
    using GlobalConfiguration = Hangfire.GlobalConfiguration;
    using Path = System.IO.Path;
    
    [assembly: OwinStartup(typeof(WebApiServer.Startup))]
    
    namespace WebApiServer
    {
        /// <summary>
        /// Owin开始文件
        /// </summary>
        public class Startup
        {
            /// <summary>
            /// Owin配置
            /// </summary>
            /// <param name="app"></param>
            public void Configuration(IAppBuilder app)
            {
                string serverBasePath = System.Web.Hosting.HostingEnvironment.MapPath("~/");
                if (string.IsNullOrEmpty(serverBasePath))
                {
                    serverBasePath = AppDomain.CurrentDomain.BaseDirectory;
                }
                //配置hangfire面板路由路径
                var hangfireRoutePath = "hf";
               //通过全局配置类,配置hangfire功能,依次为
               /*sqlserver静态存储,其中 "sqlserver_connection" 为web.config中 connectionStrings 中一项,如下
                <connectionStrings>
        <add name="sqlserver_connection" connectionString="Data Source = 10.30.0.3;Initial Catalog = GT_Informatization_V2; User ID = sa; Pwd = Bim@Center" providerName="System.Data.SqlClient" />
               </connectionStrings>
               */
               /*使用插件RecurringJobAdmin,参数:传递项目所有类引用*/ 
               /*使用插件HangfireHttpJob,参数:传递配置项,主要是面板上显示的内容*/ 
               /*使用插件Hangfire.Console,参数:面板上显示输出的背景颜色*/ GlobalConfiguration.Configuration.UseSqlServerStorage("sqlserver_connection").UseRecurringJobAdmin(true,typeof(Startup).Assembly).UseHangfireHttpJob(new HangfireHttpJobOptions()
                {
                    AddHttpJobButtonName = "添加计划任务",
                    AddRecurringJobHttpJobButtonName = "添加定时任务",
                    EditRecurringJobButtonName = "编辑定时任务",
                    PauseJobButtonName = "暂停或开始",
                    DashboardTitle = "任务管理",
                    DashboardName = "后台任务管理",
                    DashboardFooter = "后台任务管理V1.0.0.0"
                }).UseConsole(new ConsoleOptions()
                {
                    BackgroundColor = "#000079"
                });
                //强制使用中文
                 System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("zh-CN");
                //处理Hangfire.RecurringJobAdmin 插件的页面样式bug,替换原来的css文件,css文件见下个代码块
                app.Use(async (ctx, next) =>
                {
                    if (ctx.Request.Path.Value == $"/{hangfireRoutePath}/JobConfiguration/css/jobExtension")
                    {
                        await ctx.Response.WriteAsync(File.ReadAllText(AppDomain.CurrentDomain.BaseDirectory + "/css/DashBoardRecurringJobAdminfix.css"));
                    }
                    else
                    {
                        await next();
                    }
                });
                //如果不使用数据库持久化存储, 可使用内存存储记录任务,缺点是项目重启后, 数据会丢失,此配置需要安装Hangfire.MemoryStorage包
                // GlobalConfiguration.Configuration.UseMemoryStorage();
                //启用HangfireServer这个中间件(它会自动释放)
                var queues = new List<string> { "default", "apis", "recurring" }.ToArray();
                app.UseHangfireServer(new BackgroundJobServerOptions
                {
                    ServerTimeout = TimeSpan.FromMinutes(4),
                    SchedulePollingInterval = TimeSpan.FromSeconds(15),//秒级任务需要配置短点,一般任务可以配置默认时间,默认15秒
                    ShutdownTimeout = TimeSpan.FromMinutes(30),//超时时间
                    Queues = queues,//队列
                    WorkerCount = Math.Max(Environment.ProcessorCount, 40)//工作线程数,当前允许的最大线程,默认20
                });
                //配置面板登录认证
                var options = new DashboardOptions
                {
                    Authorization = new[] {
                        new BasicAuthAuthorizationFilter(
                  			new BasicAuthAuthorizationFilterOptions
                  			{
                  			    SslRedirect = false,
                  			    // Require secure connection for dashboard
                  			    RequireSsl = false,
                  			    // Case sensitive login checking
                  			    LoginCaseSensitive = false,
                  			    // Users
                  			    Users = new[]
                  			    {
                  			          new BasicAuthAuthorizationUser
                  			          {
                  			              Login = "admin",//用户名
                  			              PasswordClear = "Admin@123"//密码
                  			          }
                  			    }
                  			}
                        )
                    }
                };
                //启用Hangfire的仪表盘(可以看到任务的状态,进度等信息,结合插件可以添加任务) 
                app.UseHangfireDashboard($"/{hangfireRoutePath}", options);
                
                
               //项目其他配置项....
                
            }
    
        }
    }

    DashBoardRecurringJobAdminfix.css 文件内容如下:

    /** Main */
    #app {
        position: relative !important;
        left: 0px !important;
    }
    
    #app > div > div {
        margin-top: 30px !important;
        width: 1626px !important
    }
    
    /* Modal */
    .modal-mask {
        position: fixed;
        z-index: 9998;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, .5);
        display: table;
        transition: opacity .3s ease;
    }
    
    .modal-wrapper {
        display: table-cell;
        vertical-align: middle;
    }
    
    .modal-container {
        width: 500px;
        margin: 0px auto;
        padding: 20px 30px;
        background-color: #fff;
        border-radius: 2px;
        box-shadow: 0 2px 8px rgba(0, 0, 0, .33);
        transition: all .3s ease;
        font-family: Helvetica, Arial, sans-serif;
    }
    
    .modal-header h3 {
        margin-top: 0;
        color: #42b983;
    }
    
    .modal-body {
        margin: 20px 0;
    }
    
    .modal-default-button {
        float: right;
    }
    
    /*
        * The following styles are auto-applied to elements with
        * transition="modal" when their visibility is toggled
        * by Vue.js.
        *
        * You can easily play with the modal transition by editing
        * these styles.
        */
    
    .modal-enter {
        opacity: 0;
    }
    
    .modal-leave-active {
        opacity: 0;
    }
    
    .modal-enter .modal-container, .modal-leave-active .modal-container {
        -webkit-transform: scale(1.1);
        transform: scale(1.1);
    }
    
    /* Customize check */
    .check-container {
        width: 50px;
        display: block;
        position: relative;
        padding-left: 35px;
        margin-bottom: 10px;
        cursor: pointer;
        font-size: 18px;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        user-select: none;
    }
    
    .check-container input {
        position: absolute;
        opacity: 0;
        cursor: pointer;
        height: 0;
        width: 0;
    }
    
    .check-container .checkmark {
        position: absolute;
        top: 0;
        left: 0;
        height: 25px;
        width: 25px;
        background-color: #eee;
    }
    
    .check-container:hover input ~ .check-container .checkmark {
        background-color: #ccc;
    }
    
    .check-container input:checked ~ .checkmark {
        background-color: #2196F3;
    }
    
    .checkmark:after {
        content: "";
        position: absolute;
        display: none;
    }
    
    .check-container input:checked ~ .checkmark:after {
        display: block;
    }
    
    .check-container .checkmark:after {
        left: 9px;
        top: 5px;
        width: 5px;
        height: 10px;
        border: solid white;
        border-width: 0 3px 3px 0;
        -webkit-transform: rotate(45deg);
        -ms-transform: rotate(45deg);
        transform: rotate(45deg);
    }
    
    .check-container input:disabled ~ .checkmark {
        background-color: #dddddd !important;
    }
    
    /* CronInput */
    .cronContainer {
        position: relative;
    }
    
    .cronContainer input[type=text] {
        width: 96%;
    }
    
    .cronContainer button {
        position: absolute;
        right: 0px;
        height: 34px;
    }
    
    .cronContainer button:hover, .cronContainer button:active, .cronContainer button:focus {
        height: 34px !important;
        outline: none !important;
    }

三. 服务使用

(一). 通过SDK代码调用(通用)

1. 普通Job官方 SDK

(1) 即时任务

//用户自定义类,自定义函数, 静态类,静态方法也可
public class HF
{
    public string Test(string a)
    {
        return a;
    }
}

//方式一
var client = new BackgroundJobClient();
//第一个参数标识队列名, 可不传, 默认为default队列,不同的队列使用不同的处理器(核心)进行处理
//返回值为该任务的唯一标识Id
client.Enqueue("queueName",() => Console.WriteLine("Easy!"));
client.Enqueue("queueName",()=>new HF().Test("aaa"));

//方式二(推荐)
//第一个参数标识队列名, 可不传, 默认为default队列,不同的队列使用不同的处理器(核心)进行处理
//返回值为该任务的唯一标识Id
BackgroundJob.Enqueue("queueName",() => Console.WriteLine("Hello!"));
BackgroundJob.Enqueue("queueName",() => new HF().Test("aaa"));

(2) 延时任务

//用户自定义类,自定义函数, 静态类,静态方法也可
public class HF
{
    public string Test(string a)
    {
        return a;
    }
}

//方式一
var client = new BackgroundJobClient();
//第一个参数标识队列名, 可不传, 默认为default队列,不同的队列使用不同的处理器(核心)进行处理
//返回值为该任务的唯一标识Id
client.Schedule("queueName",() => Console.WriteLine("Reliable!"), TimeSpan.FromDays(1));
client.Schedule("queueName",()=>new HF().Test("aaa"), TimeSpan.FromDays(1));
client.Schedule("queueName",()=>new HF().Test("aaa"), new DateTime(2023, 12, 25, 10, 30, 0));
client.Schedule("queueName",()=>new HF().Test("aaa"), Convert.ToDateTime("2020.12.11 12:30:01"));
client.Schedule("queueName",()=>new HF().Test("aaa"), DateTimeOffset.Parse("2020.12.11 12:30:01"));

//方式二(推荐)
//第一个参数标识队列名, 可不传, 默认为default队列,不同的队列使用不同的处理器(核心)进行处理
//返回值为该任务的唯一标识Id
BackgroundJob.Schedule("queueName",() => Console.WriteLine("Reliable!"), TimeSpan.FromDays(1));
BackgroundJob.Schedule("queueName",() => new HF().Test("aaa"), TimeSpan.FromDays(1));
BackgroundJob.Schedule("queueName",() => new HF().Test("aaa"), new DateTime(2023, 12, 25, 10, 30, 0));
BackgroundJob.Schedule("queueName"() => new HF().Test("aaa"), Convert.ToDateTime("2020.12.11 12:30:01"));
BackgroundJob.Schedule("queueName"() => new HF().Test("aaa"), DateTimeOffset.Parse("2020.12.11 12:30:01"));

(3) 周期任务

//第一个参数是周期任务的Id,id重复会更新任务
//第二个参数是队列名称,不填,默认是default
//第三个参数是间隔的内置枚举,或者是cron表达式,cron表达式规则,见下本文 三->(三). Cron表达式详解
RecurringJob.AddOrUpdate("jobId","queueName",() => Console.WriteLine("Daily Job"), Cron.Daily);
RecurringJob.AddOrUpdate("jobId","queueName",() => Console.WriteLine("Daily Job"), "30 10 1 * * ?" );

//如果存在就删除周期任务
RecurringJob.RemoveIfExists("jobId");
//触发周期任务
RecurringJob.TriggerJob("jobId");

(4) 连续任务

var id = BackgroundJob.Enqueue(() => Console.WriteLine("Hello, "));
BackgroundJob.ContinueWith(id, () => Console.WriteLine("world!"));
2. HttpJob SDK

(1) 即时/延时任务

// serverUrl是hangfire dashbord的访问地址
var serverUrl = "http://localhost:5000/hf";
//下面用的是同步的方式,也可以使用异步: await HangfireJobClient.AddBackgroundJobAsync
var result = HangfireJobClient.AddBackgroundJob(serverUrl, new BackgroundJob
{
    JobName = "测试api",
    Method = "Get",
    Url = "http://localhost:5000/testaaa"
    SendSuccess = true,
    DelayFromMinutes = 1 //这里是在多少分钟后执行
   //RunAt = new DateTime(2020,7,25,10,5,1) // 也可以不用指定 DelayFromMinutes 参数 直接指定在什么时候运行。
}, new HangfireServerPostOption
{
    BasicUserName = "admin",//这里是hangfire设置的basicauth
    BasicPassword = "test"//这里是hangfire设置的basicauth
});

//result.JobId 就是这个一次性job的id 可以通过这个id在它没有运行前删除它
//删除即时任务
var result = HangfireJobClient.RemoveBackgroundJob(serverUrl, jobId, new HangfireServerPostOption
{
    BasicUserName = "admin",//这里是hangfire设置的basicauth
    BasicPassword = "test"//这里是hangfire设置的basicauth
});
//注意: BackgroundJob 不支持修改。如果创建后要修改 就得删除后重新创建

(2) 周期性任务

// serverUrl是hangfire dashbord的访问地址
var serverUrl = "http://localhost:5000/hf";
//下面用的是同步的方式,也可以使用异步: await HangfireJobClient.AddRecurringJobAsync
var result = HangfireJobClient.AddRecurringJob(serverUrl, new RecurringJob()
{
    JobName = "测试5点40执行",
    Method = "Post",
    Data = new {name = "aaa",age = 10},
    Url = "http://localhost:5000/testpost"
    Cron = "40 17 * * *"
}, new HangfireServerPostOption
{
    BasicUserName = "admin",//这里是hangfire设置的basicauth
    BasicPassword = "test"//这里是hangfire设置的basicauth
});
//删除周期任务
var result = HangfireJobClient.RemoveRecurringJob(serverUrl, jobId, new HangfireServerPostOption
{
    BasicUserName = "admin",//这里是hangfire设置的basicauth
    BasicPassword = "test"//这里是hangfire设置的basicauth
});
//注意: RecurringJob如果同一个JobName多次调用创建 就等同于修改。删除可以调用 RemoveRecurringJob 进行删除

(二). 通过面板进行使用(局限)

1. 函数式Job - 周期性任务

引用并正确配置使用了Hangfire.RecurringJobAdmin 方可在面板上添加任务, 添加的任务函数,必须为提前写好的函数。

① 顶部切换到Job Configuration 后, 在下方点击 Add new Job 即可弹出添加任务按钮, 该插件只支持添加周期性任务.

image-20240111174958092

参数说明:

参数 说明
JobId 任务Id
Cron 执行周期Cron表达式, 点击后面UI, 可以弹出UI编辑界面
Timezone 时区,下拉选择
Class Job函数所在的类名, 要写全称,从命名空间到类名, 例: Hangfire.RecurringJob.MyClass
Method 函数名,不带括号
Queue 指定任务执行的队列

注意事项:

//通过此插件进行添加的任务函数, 要求函数访问级别是public, 且函数不能有参数和返回值
public class MyClass{
    public void MyJob(){
        Console.WriteLine("我是Job,我被执行了!");
    }
}

② 编辑\停止任务

每条任务的后方可以编辑\停止任务, 如下图所示

image

2. HttpJob - 即时/延时任务

① 进入hangfire的后台 点击 左侧栏【计划】, 会看到一个按钮名称叫 【新增常规作业】如下图所示:

image

② 点击【新增常规作业】会出现一个json编辑器

如下图所示 image

json编辑器的参数说明如下

字段名称 备注
JobName 你给这个httpjob起的名称【必填项】
Method 这个httpjob的请求方式 “get” 或者 “post” 【必填项】
ContentType 这个httpjob的请求ContentType 默认”application/json” 【必填项】
Url 这个httpjob的请求url 【必填项】
DelayFromMinutes 需要延迟执行的分钟,==注意:0 代表立刻执行; -1代表只能手动触发; >=1代表延迟分钟数== 【必填项】
Data 这个httpjob在Method=“post”的时候可以指定post的内容,可以是一个对象也可以是一个string或者其他类型
Timeout 这个httpjob请求的超时时间(单位是毫秒 例如5000 代表是5秒)
BasicUserName 这个httpjob请求需要启用basic认证时设置的username
BasicPassword 这个httpjob请求需要启用basic认证时设置的密码
EnableRetry 失败的时候(比如超时 远程服务器请求错误等)是否启用重试 默认false
RetryTimes 错误尝试次数自定义,EnableRetry=true的时候启用
RetryDelaysInSeconds 失败重试区间,半角逗号隔开,EnableRetry=true的时候启用
SendSucMail 这个httpjob请求无异常的时候是否发送通知邮件 默认false, 如要使用邮件功能, 需要单独配置邮件服务,详见插件官方wiki
SendFaiMail 这个httpjob请求异常的时候是否发送通知邮件 默认true, 如要使用邮件功能, 需要单独配置邮件服务,详见插件官方wiki
Mail 设置通知邮件地址 如果有多个用半角逗号隔开, 如要使用邮件功能, 需要单独配置邮件服务,详见插件官方wiki
AgentClass 如果是==AgentJob开发的httpjob== 则需要填写,填写的是完整的类型格式{namespace},{程序集的名称} 例如:TestHangfireAgent.Jobs,TestHangfireAgent
AgentTimeout 如果是AgentJob开发的job 可以配置允许job在agent里异步运行允许的最大时长。为0代表不限制
CallbackEL el表达式来判断返回体是否成功还是失败,请查看wiki专门针对这个功能的介绍

③ 举例

{
  "JobName": "checkOrder",                  //Job名称
  "Method": "POST",                         //http请求的方法 
  "ContentType": "application/json",        //http参数类型
  "Url": "http://localhost:5000/checkOrder",//接口的地址 
  "DelayFromMinutes": 15,                   //15分钟后执行
  "Data": {
    "OrderId":123456                        //传的参数 orderId
  },
  "Timeout": 5000,                          //http调用超时设置
  "BasicUserName": "admin",                 //http调用的basicAuth
  "BasicPassword": "test",                  //http调用的basicAuth
  "EnableRetry": false,
  "RetryTimes" : 3 ,                        //错误尝试次数自定义,EnableRetry=true的时候启用
  "RetryDelaysInSeconds" : "5,20,30"        //失败重试区间,半角逗号隔开,EnableRetry=true的时候启用
  "SendSucMail": false,
  "SendFaiMail": true,                      //http失败时发邮件通知
  "Mail": "1877682825@qq.com",              //http调用失败通知我
  "AgentClass": ""
}

④ 点击【提交】添加job 成功如下图所示:

image

可以看到会在15分钟后执行该job

image

针对该job 可以看到有四个按钮

按钮名称 说明
加入队列 如果你想让这个job立即执行 可以点击该按钮
删除选中 如果你想删除这个job 可以点击该按钮
带参数执行 这个按钮的作用是 重新定义JSON里面的Data的值 并立即执行的意思,==在AgentJob场景下使用较多==
停止Job ==这个只能在AgentJob才可以使用==

job到时执行 如下图所示

image

job执行完毕 在完成列表可以查询

image

点击job编号进入job详情页查看具体执行情况和日志

image

3. HttpJob - 周期性任务
  1. 进入hangfire的后台 点击 上侧栏【周期性作业】

如下图所示: image

针对周期性作业 可以看到有三个按钮

按钮名称 说明
新增周期性作业 新增一个周期性httpjob作业
编辑周期性作业 如果你重新编辑该周期性httpjob作业内容可以点击该按钮
Cron表达式生成 跳转到Cron表达式生成页面
  1. 生成Cron表达式

点击【Cron表达式生成】 进入Cron表达式生成页面 如下图: image

例如 我想要 每天晚上8点05分执行

image

image

image

得出结果:【5 20 * * * 】

2.新增周期性作业

点击【新增周期性作业】按钮 会出现一个json编辑器

image

json编辑器的参数说明如下

字段名称 备注
JobName 你给这个httpjob起的名称(别名)【必填项】
RecurringJobIdentifier 唯一id 如果你不填的话 就等于JobName 【周期性job的修改是根据这个唯一id来修改的,如果你编辑修改某个周期性job的时候修改了这个id那么提交修改会创建一个新的周期性job】
Method 这个httpjob的请求方式 “get” 或者 “post” 【必填项】
ContentType 这个httpjob的请求ContentType 默认”application/json” 【必填项】
Url 这个httpjob的请求url 【必填项】
Cron Cron表达式 可以先用【Cron表达式生成】功能生成好,如果为空则代表只能是手动出发执行!
Data 这个httpjob在Method=“post”的时候可以指定post的内容,可以是一个对象也可以是一个string或者其他类型
Timeout 这个httpjob请求的超时时间(单位是毫秒 例如5000 代表是5秒)
BasicUserName 这个httpjob请求需要启用basic认证时设置的username
BasicPassword 这个httpjob请求需要启用basic认证时设置的密码
EnableRetry 失败的时候(比如超时 远程服务器请求错误等)是否启用重试 默认false
RetryTimes 错误尝试次数自定义,EnableRetry=true的时候启用
RetryDelaysInSeconds 失败重试区间,半角逗号隔开,EnableRetry=true的时候启用
SendSucMail 这个httpjob请求无异常的时候是否发送通知邮件 默认false
SendFaiMail 这个httpjob请求异常的时候是否发送通知邮件 默认true
Mail 设置通知邮件地址 如果有多个用半角逗号隔开
AgentClass 如果是==AgentJob开发的httpjob== 则需要填写,填写的是完整的类型格式{namespace},{程序集的名称} 例如:TestHangfireAgent.Jobs.XXXJOB,TestHangfireAgent
AgentTimeout 如果是AgentJob开发的job 可以配置允许job在agent里异步运行允许的最大时长。为0代表不限制
CallbackEL el表达式来判断返回体是否成功还是失败,请查看wiki专门针对这个功能的介绍

image

举例

{
  "JobName": "scoreOrder",                  //Job名称
  "Method": "POST",                         //http请求的方法 
  "ContentType": "application/json",        //http参数类型
  "Url": "http://localhost:5000/scoreOrder",//接口的地址 
  "Cron": "5 20 * * *",                     //每天晚上8点05分执行
  "Data": {
    "Hour":48                               //传的参数超过48小时
  },
  "Timeout": 5000,                          //http调用超时设置
  "BasicUserName": "admin",                 //http调用的basicAuth
  "BasicPassword": "test",                  //http调用的basicAuth
  "EnableRetry": false,                     //http调用失败的错误重试
  "RetryTimes" : 3 ,                        //错误尝试次数自定义,EnableRetry=true的时候启用
  "RetryDelaysInSeconds" : "5,20,30"        //失败重试区间,半角逗号隔开,EnableRetry=true的时候启用
  "SendSucMail": false,
  "SendFaiMail": true,                      //http失败时发邮件通知
  "Mail": "1877682825@qq.com",              //http调用失败通知我
  "AgentClass": ""
}

image

点击【提交】

image

添加成功 在job列表可以查

我刚才设置的是20:05分执行

目前时间是19:50

正好是还有15分钟就要执行了 说明Cron表达式没有问题 image

对于周期性job 有3个按钮可以操作

按钮名称 说明
立即执行 如果你希望不要等到8点05想立即就执行可以点击它
停止或开始任务 如果你希望暂停这个周期性job 可以点它,点完之后再次点击就是开启
编辑周期任务 如果你刚刚添加的参数有错误,可以点击这个按钮重新编辑提交

image

停止或开始任务

如果一个周期性job是暂停的 会以红色字体展示 image 注意:点击停止就会把当前job的cron设置为空也就会暂停hangfire对于这个job的调度了。 注意:点击开始,会重新恢复你之前设置的cron,则hangfire就会重新调度了

编辑周期任务

点击提交修改 image

周期性job执行

image

周期性job执行完毕 在完成列表可以查询

image

点击job编号进入job详情页查看具体执行情况和日志

image

(三). Cron表达式详解

cron表达式格式:
{秒数} {分钟} {小时} {日期} {月份} {星期} {年份(可为空)}
例 “0 0 12 ? * WED” 在每星期三下午12:00 执行(年份通常 省略)

  1. 每个字段的允许值:

    字段 允许值 允许的特殊字符
    0-59 - * /
    0-59 - * /
    小时 0-23 - * /
    日期 1-31 - * ? / L W C
    月份 1-12 或者 JAN-DEC - * /
    星期 1-7 或者 SUN-SAT - * ? / L C #
    年(可选) 留空, 1970-2099 - * /
  2. 允许值的解释:

    位置 解释
    Seconds (秒) 可以用数字0-59 表示,
    Minutes(分) 可以用数字0-59 表示,
    Hours(时) 可以用数字0-23表示,
    Day-of-Month(天) 可以用数字1-31 中的任一一个值,但要注意一些特别的月份
    Month(月) 可以用0-11 或用字符串 “JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV and DEC” 表示
    Day-of-Week(每周) 可以用数字1-7表示(1 = 星期日)或用字符口串“SUN, MON, TUE, WED, THU, FRI and SAT”表示
  3. 每个符号的意义:

    * 表示所有值;
    ? 表示未说明的值,即不关心它为何值;
    - 表示一个指定的范围;
    , 表示附加一个可能值;
    / 符号前表示开始时间,符号后表示每次递增的值;
    L(“last”) (“last”) “L” 用在day-of-month字段意思是 “这个月最后一天”;用在 day-of-week字段, 它简单意思是 “7” or “SAT”。 如果在day-of-week字段里和数字联合使用,它的意思就是 “这个月的最后一个星期几” – 例如: “6L” means “这个月的最后一个星期五”. 当我们用“L”时,不指明一个列表值或者范围是很重要的,不然的话,我们会得到一些意想不到的结果。
    W(“weekday”) 只能用在day-of-month字段。用来描叙最接近指定天的工作日(周一到周五)。例如:在day-of-month字段用“15W”指“最接近这个 月第15天的工作日”,即如果这个月第15天是周六,那么触发器将会在这个月第14天即周五触发;如果这个月第15天是周日,那么触发器将会在这个月第 16天即周一触发;如果这个月第15天是周二,那么就在触发器这天触发。注意一点:这个用法只会在当前月计算值,不会越过当前月。“W”字符仅能在 day-of-month指明一天,不能是一个范围或列表。也可以用“LW”来指定这个月的最后一个工作日。
    # 只能用在day-of-week字段。用来指定这个月的第几个周几。例:在day-of-week字段用”6#3”指这个月第3个周五(6指周五,3指第3个)。如果指定的日期不存在,触发器就不会触发。
    C 指和calendar联系后计算过的值。例:在day-of-month 字段用“5C”指在这个月第5天或之后包括calendar的第一天;在day-of-week字段用“1C”指在这周日或之后包括calendar的第一天。

  4. 一些cron表达式案例:

    表达式 解释
    */5 * * * * ? 每隔5秒执行一次
    0 */1 * * * ? 每隔1分钟执行一次
    0 0 5-15 * * ? 每天5-15点整点触发
    0 0/3 * * * ? 每三分钟触发一次
    0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发
    0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发
    0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
    0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
    0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
    0 0 12 ? * WED 表示每个星期三中午12点
    0 0 17 ? * TUES,THUR,SAT 每周二、四、六下午五点
    0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44触发
    0 15 10 ? * MON-FRI 周一至周五的上午10:15触发
    0 0 23 L * ? 每月最后一天23点执行一次
    0 15 10 L * ? 每月最后一日的上午10:15触发
    0 15 10 ? * 6L 每月的最后一个星期五上午10:15触发
    0 15 10 * * ? 2005 2005年的每天上午10:15触发
    0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一个星期五上午10:15触发
    0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发
    30 * * * * ? 每半分钟触发任务
    30 10 * * * ? 每小时的10分30秒触发任务
    30 10 1 * * ? 每天1点10分30秒触发任务
    30 10 1 20 * ? 每月20号1点10分30秒触发任务
    30 10 1 20 10 ? * 每年10月20号1点10分30秒触发任务
    30 10 1 20 10 ? 2011 2011年10月20号1点10分30秒触发任务
    30 10 1 ? 10 * 2011 2011年10月每天1点10分30秒触发任务
    30 10 1 ? 10 SUN 2011 2011年10月每周日1点10分30秒触发任务
    15,30,45 * * * * ? 每15秒,30秒,45秒时触发任务
    15-45 * * * * ? 15到45秒内,每秒都触发任务
    15/5 * * * * ? 每分钟的每15秒开始触发,每隔5秒触发一次
    15-30/5 * * * * ? 每分钟的15秒到30秒之间开始触发,每隔5秒触发一次
    0 0/3 * * * ? 每小时的第0分0秒开始,每三分钟触发一次
    0 15 10 ? * MON-FRI 星期一到星期五的10点15分0秒触发任务
    0 15 10 L * ? 每个月最后一天的10点15分0秒触发任务
    0 15 10 LW * ? 每个月最后一个工作日的10点15分0秒触发任务
    0 15 10 ? * 5L 每个月最后一个星期四的10点15分0秒触发任务
    0 15 10 ? * 5#3 每个月第三周的星期四的10点15分0秒触发任务
  5. 表达式生成器:

(四). 注意事项:

  1. 关于HttpJob

    • HttpJob官方建议使用此插件的Hangfire.HttpJob.Client类,通过代码添加httpjob时,此添加项目尽量不要使用IIS部署运行, 因为IIS 应用池的回收问题, 可能导致某些问题。

    • 发起HttpJob调度器只能负责定期执行http请求, 无法准确的获取http请求对应方法的执行进度、执行结果,心跳等信息, 为了解决这个问题, 官方开发了Hangfire.HttpJob.Agent 库, 此包主要是在被执行的http请求项目中引入, 随后继承相关的类 JobAgent 实现其中的OnStart函数, 在OnStart中执行具体任务, 并打印进度等等。具体可参考下方视频