C#基于Autocad的二次开发(8.Hangfire后台任务)

开始之前确保已完成步骤1创建解决方案和类库项目,并在对应项目中安装nuget包

Hangfire介绍

Hangfire是一个开源的.NET任务调度框架,提供了内置的控制台面板,任务可以支持在控制台面板手动执行,并且可以通过配置让其支持持久化存储。Hangfire与特定的.NET应用程序类型无关。你可以在Web应用程序,客户端应用程序,控制台应用程序或Windows服务中使用它。

服务注入

这里同样通过Autofac注入hangfire服务,ACD.Application项目中创建IHangfireService.cs接口文件并继承自IBaseService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public interface IHangfireService:IBaseService
{
#region 循环任务
void AddOrUpdate(Expression<Action> methodCall, Func<string> cronExpression, TimeZoneInfo timeZone = null, string queue = "default");

void AddOrUpdate<T>(Expression<Action<T>> methodCall, Func<string> cronExpression, TimeZoneInfo timeZone = null, string queue = "default");

void AddOrUpdate(Expression<Action> methodCall, string cronExpression, TimeZoneInfo timeZone = null, string queue = "default");

void AddOrUpdate<T>(Expression<Action<T>> methodCall, string cronExpression, TimeZoneInfo timeZone = null, string queue = "default");

void AddOrUpdate(Expression<Func<Task>> methodCall, Func<string> cronExpression, TimeZoneInfo timeZone = null, string queue = "default");

void AddOrUpdate<T>(Expression<Func<T, Task>> methodCall, Func<string> cronExpression, TimeZoneInfo timeZone = null, string queue = "default");

void AddOrUpdate(Expression<Func<Task>> methodCall, string cronExpression, TimeZoneInfo timeZone = null, string queue = "default");

void AddOrUpdate<T>(Expression<Func<T, Task>> methodCall, string cronExpression, TimeZoneInfo timeZone = null, string queue = "default");
#endregion

#region 队列任务
string Enqueue(Expression<Action> methodCall);

string Enqueue(Expression<Func<Task>> methodCall);

string Enqueue<T>(Expression<Action<T>> methodCall);

string Enqueue<T>(Expression<Func<T, Task>> methodCall);
#endregion

#region 延时任务
string Schedule(Expression<Action> methodCall, TimeSpan delay);

string Schedule(Expression<Func<Task>> methodCall, TimeSpan delay);

string Schedule(Expression<Action> methodCall, DateTimeOffset enqueueAt);

string Schedule(Expression<Func<Task>> methodCall, DateTimeOffset enqueueAt);

string Schedule<T>(Expression<Action<T>> methodCall, TimeSpan delay);

string Schedule<T>(Expression<Func<T, Task>> methodCall, TimeSpan delay);

string Schedule<T>(Expression<Action<T>> methodCall, DateTimeOffset enqueueAt);

string Schedule<T>(Expression<Func<T, Task>> methodCall, DateTimeOffset enqueueAt);
#endregion

bool Delete(string jobId);

bool Delete(string jobId, string fromState);

bool Requeue(string jobId);

bool Requeue(string jobId, string fromState);
}

ACD.Infrastructure项目中创建HangfireService.cs并实现自IHangfireService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public class HangfireService:IHangfireService
{
public void AddOrUpdate(Expression<Action> methodCall, Func<string> cronExpression, TimeZoneInfo timeZone = null, string queue = "default")
=> RecurringJob.AddOrUpdate(methodCall, cronExpression, timeZone, queue);

public void AddOrUpdate<T>(Expression<Action<T>> methodCall, Func<string> cronExpression, TimeZoneInfo timeZone = null, string queue = "default")
=> RecurringJob.AddOrUpdate(methodCall, cronExpression, timeZone, queue);

public void AddOrUpdate(Expression<Action> methodCall, string cronExpression, TimeZoneInfo timeZone = null, string queue = "default")
=> RecurringJob.AddOrUpdate(methodCall, cronExpression, timeZone, queue);

public void AddOrUpdate<T>(Expression<Action<T>> methodCall, string cronExpression, TimeZoneInfo timeZone = null, string queue = "default")
=> RecurringJob.AddOrUpdate(methodCall, cronExpression, timeZone, queue);

public void AddOrUpdate(Expression<Func<Task>> methodCall, Func<string> cronExpression, TimeZoneInfo timeZone = null, string queue = "default")
=> RecurringJob.AddOrUpdate(methodCall, cronExpression, timeZone, queue);

public void AddOrUpdate<T>(Expression<Func<T, Task>> methodCall, Func<string> cronExpression, TimeZoneInfo timeZone = null, string queue = "default")
=> RecurringJob.AddOrUpdate(methodCall, cronExpression, timeZone, queue);

public void AddOrUpdate(Expression<Func<Task>> methodCall, string cronExpression, TimeZoneInfo timeZone = null, string queue = "default")
=> RecurringJob.AddOrUpdate(methodCall, cronExpression, timeZone, queue);

public void AddOrUpdate<T>(Expression<Func<T, Task>> methodCall, string cronExpression, TimeZoneInfo timeZone = null, string queue = "default")
=> RecurringJob.AddOrUpdate(methodCall, cronExpression, timeZone, queue);

public bool Delete(string jobId) =>
BackgroundJob.Delete(jobId);

public bool Delete(string jobId, string fromState) =>
BackgroundJob.Delete(jobId, fromState);

public string Enqueue(Expression<Func<Task>> methodCall) =>
BackgroundJob.Enqueue(methodCall);

public string Enqueue<T>(Expression<Action<T>> methodCall) =>
BackgroundJob.Enqueue(methodCall);

public string Enqueue(Expression<Action> methodCall) =>
BackgroundJob.Enqueue(methodCall);

public string Enqueue<T>(Expression<Func<T, Task>> methodCall) =>
BackgroundJob.Enqueue(methodCall);

public bool Requeue(string jobId) =>
BackgroundJob.Requeue(jobId);

public bool Requeue(string jobId, string fromState) =>
BackgroundJob.Requeue(jobId, fromState);

public string Schedule(Expression<Action> methodCall, TimeSpan delay) =>
BackgroundJob.Schedule(methodCall, delay);

public string Schedule(Expression<Func<Task>> methodCall, TimeSpan delay) =>
BackgroundJob.Schedule(methodCall, delay);

public string Schedule(Expression<Action> methodCall, DateTimeOffset enqueueAt) =>
BackgroundJob.Schedule(methodCall, enqueueAt);

public string Schedule(Expression<Func<Task>> methodCall, DateTimeOffset enqueueAt) =>
BackgroundJob.Schedule(methodCall, enqueueAt);

public string Schedule<T>(Expression<Action<T>> methodCall, TimeSpan delay) =>
BackgroundJob.Schedule(methodCall, delay);

public string Schedule<T>(Expression<Func<T, Task>> methodCall, TimeSpan delay) =>
BackgroundJob.Schedule(methodCall, delay);

public string Schedule<T>(Expression<Action<T>> methodCall, DateTimeOffset enqueueAt) =>
BackgroundJob.Schedule(methodCall, enqueueAt);

public string Schedule<T>(Expression<Func<T, Task>> methodCall, DateTimeOffset enqueueAt) =>
BackgroundJob.Schedule(methodCall, enqueueAt);
}

项目ACD.Domain中的配置项实体类AppSettingConfig添加字段保存hangfire的数据库链接字符串

1
2
3
4
5
6
7
8
9
10
/// <summary>
/// 配置项
/// </summary>
public class AppSettingConfig
{
/// <summary>
/// hangfire 数据库连接
/// </summary>
public string HangfireStorage { get; set; }
}

ACD.Infrastructure项目中Startup静态类中实现Hangfire的注入配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
public static class Startup
{
public static ContainerBuilder AddInfrastructure(this ContainerBuilder builder)
{
builder.RegisterModule<NLogModule>();

return builder
.AddServices();
}

public static BackgroundJobServer AddHangfireService(this IGlobalConfiguration configuration, AppSettingConfig config)
{
configuration
.SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
.UseSimpleAssemblyNameTypeSerializer()
.UseRecommendedSerializerSettings()
.UseSqlServerStorage(config.HangfireStorage, new SqlServerStorageOptions
{
CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
QueuePollInterval = TimeSpan.Zero,
UseRecommendedIsolationLevel = true
});

return new BackgroundJobServer();
}

/// <summary>
/// 配置注入
/// </summary>
/// <param name="builder"></param>
/// <param name="configFileName"></param>
/// <returns></returns>
/// <exception cref="FileNotFoundException"></exception>
public static async Task<AppSettingConfig> AddAppConfig(this ContainerBuilder builder, string configFileName = "appsettings.json")
{
var configFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, configFileName);

if (!File.Exists(configFile)) throw new FileNotFoundException($"{configFileName} 配置文件不存在");

string jsonString;
using (var stream = File.OpenRead(configFile))
{
var reader = new StreamReader(stream);
jsonString = await reader.ReadToEndAsync();
}

var appSetting = JsonConvert.DeserializeObject<AppSettingConfig>(jsonString);

builder
.RegisterInstance(appSetting)
.SingleInstance();

return appSetting;
}

/// <summary>
/// 服务注入
/// </summary>
/// <param name="builder"></param>
/// <returns></returns>
private static ContainerBuilder AddServices(this ContainerBuilder builder)
{
var interfaceType = typeof(IBaseService);

//通过表达式过滤不需要加载的程序集
var types =
AppDomain.CurrentDomain.GetReferanceAssemblies(x => x.FullName.StartsWith("ACD."));

var interfaceTypes = types
.SelectMany(s => s.GetTypes())
.Where(t => interfaceType.IsAssignableFrom(t)
&& t.IsClass && !t.IsAbstract)
.Select(t => new
{
Service = t.GetInterfaces().FirstOrDefault(),
Implementation = t
})
.Where(t => t.Service != null
&& interfaceType.IsAssignableFrom(t.Service));

foreach (var type in interfaceTypes)
{
//这里默认为瞬时注入,可根据传入参数决定注入的生命周期
//或者将IBaseService拆分为三个接口,对应三个不同的生命周期分别注入
builder.RegisterType(type.Implementation)
.As(type.Service)
.InstancePerDependency();
}

return builder;
}
}

ACD.Client项目中AppInit.cs调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
internal class AppInit
{
private static IContainer Container;

internal static IDisposable BackgroundJobServers;

internal static async Task Init()
{
var builder = new ContainerBuilder();

var config = await builder.AddAppConfig("appsettings.json");

BackgroundJobServers = GlobalConfiguration.Configuration.AddHangfireService(config);

builder.AddInfrastructure();

Container = builder.Build();
}

internal static T Resolve<T>() => Container.Resolve<T>();
}

使用

1
2
3
4
5
6
IHangfireService hangfire = AppInit.Resolve<IHangfireService>();

hangfire.AddOrUpdate(() => Console.WriteLine("hello"), "*/2 * * * * * ", TimeZoneInfo.Local);

//程序终止时通过调用AppInit.BackgroundJobServers?.Dispose()释放资源

ACD.Client项目中的配置文件appsettings.json修改如下:

1
2
3
{
"HangfireStorage": "Data Source=localhost;Initial Catalog = hangfire;User Id = sa;Password = 123456;"
}

hangfire可配置项较多,这里只做了持久化的配置,还有其他例如控制面板的路由以及凭证配置等,感兴趣可以参阅hangfire的官方文档或其他资源进一步完善。


C#基于Autocad的二次开发(8.Hangfire后台任务)
https://wangyuangen.github.io/2024/05/20/CsharpAutocad8/
作者
Yuangen Wang
发布于
2024年5月20日
许可协议