为什么介绍Hangfire
我在一个我的简单地个人项目中需要用到用户请求限制,是根据分钟限制40个请求。这就要用到定时任务地库,很显然自己来写定时任务是不机智地做法。
我去问了gpt,他给了我几个库,我从后到前逐个看文档,发现比较复杂和麻烦,直到看见了这个,我较为容易地就在我的项目中配置了定时任务,而且很好的是,还有web ui控制面板。使用效果很好。
文档地址
官网地文档地址
如下
Documentation — Hangfire Documentation
ASP.NET Core Applications — Hangfire Documentation
不过可以在书栈网找到汉化(翻译)的文档。非常不错。
书栈网hangfire地址
https://www.bookstack.cn/read/hangfire-zh/blankquick-start
如何使用
简单地来说,你可以简单地按照官网来操作。
ASP.NET Core Applications — Hangfire Documentation
首先是你需要安装依赖包
你可以在nugut中手动安装,下面对应的包。
<PackageReference Include="Hangfire.Core" Version="1.8.*" /> <PackageReference Include="Hangfire.SqlServer" Version="1.8.*" /> <PackageReference Include="Hangfire.AspNetCore" Version="1.8.*" /> <PackageReference Include="Microsoft.Data.SqlClient" Version="*" />
不过一种更快速地方法是,手动编辑项目文件,然后保存即可。
vs
双击要安装依赖包地项目
rider
编辑项目文件。
直接拷贝到一个ItemGroup中。
然后保存(vs),即可。
然后创建数据库
这个根据需要即可,一般比较简单。数据库使用localdb和sql server均可。
只需要创建数据库即可,不需要创建数据表。
例如,下面
配置连接字符串。
在appsettings.json 中添加
"ConnectionStrings": { "HangfireConnection": "Server=127.0.0.1,1433;Database=HangfireJobs;MultipleActiveResultSets=True;User=suzumiya;Password=Sakura@99;TrustServerCertificate=true;" }
Database按照自己创建的来写。用户名和密码也按照自己的。
此外,你还需要配置数据库的tcp/ip连接。或者你可以使用windows身份验证的连接字符串。
例如
"ConnectionStrings": { "HangfireConnection": "Server=DESKTOP-6ACSBNS;Database=HangfireJobs;MultipleActiveResultSets=True;Integrated Security=True;TrustServerCertificate=true;" }
其中的Server要填写连接Sql server时用到的服务器名称(不具备迁移性)。
编辑Program.cs文件进行配置。
using Hangfire;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
// 进行基本配置,注意其中的HangfireConnection需要和前面的数据库连接字符串的名字一样。
builder.Services.AddHangfire(configuration => configuration
.SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseSqlServerStorage(builder.Configuration.GetConnectionString("HangfireConnection")));
// 配置服务
builder.Services.AddHangfireServer();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
// 默认情况下,仅允许本地访问 Hangfire 仪表板。必须配置仪表板授权才能允许远程访问
// 这里添加了一个连接地址,表明使用新的地址进行访问,而不是默认地址。
//默认地址是hangfire
app.UseHangfireDashboard("/dashboard");
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapDefaultControllerRoute();
// Map hangfire的端点
endpoints.MapHangfireDashboard();
});
app.Run();
启动程序,可以直接访问。
配置任务
在前面,我们已经能够访问仪表盘,这就意味着我们完成了最基本的架构,紧接着我们就可以在这个框架上进行配置任务进行使用了。
配置入门任务(单次执行)
在app.run();前面添加下面的代码。
var backgroundJobs = app.Services.GetRequiredService<IBackgroundJobClient>();
backgroundJobs.Enqueue(() => Console.WriteLine("Hello world from Hangfire!"));
// RecurringJob
app.Run();
不过,直接使用
BackgroundJob.Enqueue(() => Console.WriteLine("Hello world from Hangfire!"));
也是可以的,因为BackgroundJob.Enqueue是一个静态方法。
启动程序,观察效果。
注意,启动程序时选择和项目同名的自托管的方式。可以观察到输出。(配置的任务一般不会是控制台输出)。
可以在最后一行看到 Hello world from Hangfire!
同时在
/dashboard/jobs/succeeded
看到已经完成的任务。
文档
阅读文档地址,可以使用更多细节。
https://docs.hangfire.io/en/latest/background-methods/index.html
后台任务
HelloMaker
public class HelloMaker
{
private readonly string? _name;
public HelloMaker()
{
}
public HelloMaker(string? name)
{
_name = name;
}
public void Joker()
{
Console.WriteLine(_name+" Ha haha!");
}
}
配置后台任务
backgroundJobs.Enqueue<HelloMaker>(maker => maker.Joker());
启动
我们可以 观察到两个输出,这是因为前面我没有添加空构造函数。
任务被存储,然后又在配置好后重新运行。
单击#6对应的作业
可以在里面找到错误信息。
延迟任务
官网比较详细,不再过多介绍
使用代码
BackgroundJob.Enqueue<HelloMaker>(maker => maker.Joker()); BackgroundJob.Schedule<HelloMaker>(maker => maker.Joker(),TimeSpan.FromSeconds(2));
修改前面的HelloMaker对象(需要配置为单例)
public void Joker() { Console.WriteLine(_name+" Ha haha!"+DateTime.Now); }
执行,可以看到下面的输出
Ha haha!2023/8/27 20:38:00
Ha haha!2023/8/27 20:38:15
有点怪异,配置的延迟2秒,实际执行延迟了那么多。
查看时发现,只能认为是可接受的。
重复任务
代码
RecurringJob.AddOrUpdate("joker", ()=> new HelloMaker("John").Joker(),Cron.Minutely);
一个遗憾是最低只能配置按分钟执行的作业/任务。
输出:
Ha haha!2023/8/27 20:55:06
Ha haha!2023/8/27 20:56:06
并没有发现John的身影。
添加默认值
public HelloMaker() { _name = "default"; }
之后,效果是这样的。(手动执行)
default Ha haha!2023/8/27 20:58:55
default Ha haha!2023/8/27 20:58:56
default Ha haha!2023/8/27 20:59:01
虽然每个对象都是新的,我期望他会使用构造函数提供的值,他却没有。
可以在
Calling Methods in Background — Hangfire Documentation
看到它的工作流程。
使用依赖注入
一个很尴尬的事情是,hangfire的asp.net core的包使用的就是asp.net 的容器进行依赖注入,我反而去hangfire找了很久其他的容器。最终找到了一个可用的容器。但是我把那个取消之后发现依旧可用。
所以依赖注入直接按照asp.net core的规则即可。例如
RecurringJob.AddOrUpdate<HelloMaker>("newMaker",hel=>hel.Joker(),Cron.Minutely);
到了事件后就会自动从容器中找一个对象然后执行。
一个作业(任务)是什么样的?
using HangfireHub.Models;
var helloMaker = Activate<HelloMaker>();
helloMaker.Joker();
Activate 就像一个
app.Services.GetRequiredService<T>()一样 /单例app.Services.CreateScope().ServiceProvider.GetRequiredService<>()作用域
会在到达要执行的时间时创建(获取)执行。
扩展
另外的容器。
添加一些代码,像下面一样即可。
需要的依赖是
<PackageReference Include="Ninject" Version="3.3.6" />
<PackageReference Include="Ninject.Extensions.DependencyInjection" Version="1.0.2" />
这实际上就是更换了asp.net core 提供的默认容器。
不仅可以在 kernel => 后面绑定依赖关系
还可以使用asp.net core的AddSingleton等方法。
using Hangfire;
using HangfireHub.Hangfire;
using HangfireHub.Models;
using Ninject;
using Ninject.Extensions.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
builder.Host
.UseServiceProviderFactory(new NinjectServiceProviderFactory())
.ConfigureContainer<IKernel>(kernel =>
{
kernel.Bind<Dog>().ToSelf();
kernel.Bind<Sound>().ToSelf().InSingletonScope();
});
builder.Services.AddControllersWithViews();
builder.Services.AddHangfire(configuration => configuration
.SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseSqlServerStorage(builder.Configuration.GetConnectionString("HangfireConnection")));
builder.Services.AddHangfireServer();
builder.Services.AddSingleton<HelloMaker>();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseHangfireDashboard("/dashboard");
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHangfireDashboard();
endpoints.MapDefaultControllerRoute();
});
var maker = app.Services.GetRequiredService<HelloMaker>();
maker.Prefix = "PPP: ";
// RecurringJob.AddOrUpdate<Dog>("hub",dog=>dog.WW(),Cron.Minutely);
RecurringJob.AddOrUpdate<HelloMaker>("newMaker",hel=>hel.Joker(),Cron.Minutely);
// RecurringJob
app.Run();