找回密码
 立即注册
首页 业界区 业界 Hangfire Redis 实现秒级定时任务、使用 CQRS 实现动态 ...

Hangfire Redis 实现秒级定时任务、使用 CQRS 实现动态执行代码

阴昭昭 2025-6-2 21:27:33
目录

  • 定时任务需求
  • 核心逻辑
  • 使用 Redis 实现秒级定时任务

    • 第一步
    • 第二步
    • 第三步
    • 第四步

  • 业务服务实现动态代码

    • 第一步
    • 第二步
    • 第三步
    • 第四步
    • 第五步

  • 最后

定时任务需求

本文示例项目仓库:https://github.com/whuanle/HangfireDemo

主要有两个核心需求:

  • 需要实现秒级定时任务;
  • 开发者使用定时任务要简单,不要弄复杂了;

在微服务架构中中,定时任务是最常用的基础设施组件之一,社区中有很多定时任务类库或平台,例如 Quartz.NET、xxx-job,使用方法差异很大,比如 xxx-job 的核心是 http 请求,配置定时任务实现 http 请求具体的接口,不过用起来还是比较复杂的。
在微服务中,使用的组件太多了,如果每个组件的集成都搞得很麻烦,那么服务的代码很可能会大量膨胀,并且容易出现各种 bug。以 xxx-job 为例,如果项目中有 N 个定时任务,设计 N 个 http 接口被 xxx-job 回调触发,除了 http 接口数量庞大,在各个环节中还容易出现 bug。

在近期项目需求中,刚好要用到定时任务,结合 C# 语言的特性,笔者的方法是利用 Hangfire 框架和语言特性,封装一些方法,使得开发者可以无感使用定时任务,大大简化链路和使用难度。
使用示例,结合 MediatR 框架定义 CQRS ,该 Command 将会被定时任务触发执行:
  1. public class MyTestRequest : HangfireRequest, IRequest<ExecteTasResult>
  2. {
  3. }
  4. /// <summary>
  5. /// 要被定时任务执行的代码.
  6. /// </summary>
  7. public class MyTestHandler : IRequestHandler<MyTestRequest, ExecteTasResult>
  8. {
  9.     public async Task<ExecteTasResult> Handle(MyTestRequest request, CancellationToken cancellationToken)
  10.     {
  11.         // 逻辑
  12.         
  13.         return new ExecteTasResult
  14.         {
  15.             CancelTask = false
  16.         };
  17.     }
  18. }
复制代码
要启动一个定时任务,只需要:
  1. private readonly SendHangfireService _hangfireService;
  2. public SendTaskController(SendHangfireService hangfireService)
  3. {
  4.         _hangfireService = hangfireService;
  5. }
  6. [HttpGet("aaa")]
  7. public async Task<string> SendAsync()
  8. {
  9.         await _hangfireService.Send(new MyTestRequest
  10.         {
  11.                 CreateTime = DateTimeOffset.Now,
  12.                 CronExpression = "* * * * * *",
  13.                 TaskId = Guid.NewGuid().ToString(),
  14.         });
  15.         return "aaa";
  16. }
复制代码
通过这种方式使用定时任务,开发者只需要使用很简单的代码即可完成需求,不需要关注细节,也不需要定义各种 http 接口,并且犹豫不需要关注使用的外部定时任务框架,所以随时可以切换不同的定时任务实现。
核心逻辑

本文示例项目仓库:whuanle/HangfireDemo

示例项目结构如下:


HangfireServer 是定时任务服务实现,HangfireServer 服务只需要暴露两个接口 addtask、cancel,分别用于添加定时任务和取消定时任务,无论什么业务的服务,都通过 addtask 服务添加。

DemoApi 则是业务服务,业务服务只需要暴露一个· execute 接口用于触发定时任务即可。

基础逻辑如下:

graph LRsubgraph DemoApiA[定义 Command] -- 序列化参数Command --> AA[发送定时任务]E[DemoApi:execute 接口] --> F[DemoApi:执行 Command]endsubgraph HangfireB[addtask] --> C[Hangfire:存储任务]C --> D[Hangfire:执行任务]D --> DD[发起请求]end  %% 同时建立必要的连接    AA -- 添加定时任务 --> B    DD -- 请求 --> E
由于项目中使用的是 MediatR 框架实现 CQRS 模式,因此很容易实现定时任务动态调用代码,只需要按照平时的 CQRS 发送定时任务命令,指定定时任务要执行的 Command 即可。
例如,有以下 Command 需要被定时任务执行:
  1. ACommand
  2. BCommand
  3. CCommand
复制代码
首先这些命令会被序列化为 json ,发送到 HangfireServer 服务,HangfireServer 在恰当时机将参数原封不动推送到 DemoApi 服务,DemoApi 服务拿到这些参数序列化为对应的类型,然后通过 MediatR 发送命令,即可实现任意命令的定时任务动态调用。

下面来分别实现 HangfireServer 、DemoApi 服务。
在 Shred 项目中添加以下文件。
2.png


其中 TaskRequest 内容如下,其它文件请参考示例项目。
  1. public class TaskRequest
  2. {
  3.     /// <summary>
  4.     /// 任务 id.
  5.     /// </summary>
  6.     public string TaskId { get; set; } = "";
  7.     /// <summary>
  8.     /// 定时任务要请求的服务地址或服务名称.
  9.     /// </summary>
  10.     public string ServiceName { get; set; } = "";
  11.     /// <summary>
  12.     /// 参数类型名称.
  13.     /// </summary>
  14.     public string CommandType { get; set; } = "";
  15.     /// <summary>
  16.     /// 请求参数内容,json 序列化后的字符串.
  17.     /// </summary>
  18.     public string CommandBody { get; set; } = "";
  19.     /// <summary>
  20.     /// Cron 表达式.
  21.     /// </summary>
  22.     public string CronExpression { get; set; } = "";
  23.     /// <summary>
  24.     /// 创建时间.
  25.     /// </summary>
  26.     public string CreateTime { get; set; } = "";
  27. }
复制代码
使用 Redis 实现秒级定时任务

Hangfire 本身配置比较复杂,其分布式实现对数据库性能要求比较高,因此使用 Mysql、Sqlserver 等数据库存储数据会带了很大的压力,而且要求实现秒级定时任务,NoSql 数据库可以更加好地实现这一需求,笔者这里使用 Redis 来存储任务数据。
HangfireServer 项目结构如下:
3.png


对 HangfireServer 的设计主要分为几步:

  • Hangfire 支持容器管理;
  • 配置 Hangfire ;
  • 定义 RecurringJobHandler 执行任务发起 http 请求到业务系统;
  • 定义 http 接口,接收定时任务;

引入类库:
  1. [/code]
  2. 首先是关于 Hangfire 本身的配置,现在几乎都是基于依赖注入的设计,不搞静态类型,所以我们需要实现定时任务执行器创建服务实例的,以便每次定时任务请求时,服务实例都是在一个新的容器,处以一个新的上下文中。
  3. [size=3][b]第一步[/b][/size]
  4. 创建 HangfireJobActivatorScope、HangfireActivator 两个文件,实现 Hangfire 支持容器上下文。
  5. [code]/// <summary>
  6. /// 任务容器.
  7. /// </summary>
  8. public class HangfireJobActivatorScope : JobActivatorScope
  9. {
  10.     private readonly IServiceScope _serviceScope;
  11.     private readonly string _jobId;
  12.     /// <summary>
  13.     /// Initializes a new instance of the <see cref="HangfireJobActivatorScope"/> class.
  14.     /// </summary>
  15.     /// <param name="serviceScope"></param>
  16.     /// <param name="jobId"></param>
  17.     public HangfireJobActivatorScope([NotNull] IServiceScope serviceScope, string jobId)
  18.     {
  19.         _serviceScope = serviceScope ?? throw new ArgumentNullException(nameof(serviceScope));
  20.         _jobId = jobId;
  21.     }
  22.     /// <inheritdoc/>
  23.     public override object Resolve(Type type)
  24.     {
  25.         var res = ActivatorUtilities.GetServiceOrCreateInstance(_serviceScope.ServiceProvider, type);
  26.         return res;
  27.     }
  28.     /// <inheritdoc/>
  29.     public override void DisposeScope()
  30.     {
  31.         _serviceScope.Dispose();
  32.     }
  33. }
复制代码
  1. /// <summary>
  2. /// JobActivator.
  3. /// </summary>
  4. public class HangfireActivator : JobActivator
  5. {
  6.     private readonly IServiceScopeFactory _serviceScopeFactory;
  7.     /// <summary>
  8.     /// Initializes a new instance of the <see cref="HangfireActivator"/> class.
  9.     /// </summary>
  10.     /// <param name="serviceScopeFactory"></param>
  11.     public HangfireActivator(IServiceScopeFactory serviceScopeFactory)
  12.     {
  13.         _serviceScopeFactory = serviceScopeFactory ?? throw new ArgumentNullException(nameof(serviceScopeFactory));
  14.     }
  15.     /// <inheritdoc/>
  16.     public override JobActivatorScope BeginScope(JobActivatorContext context)
  17.     {
  18.         return new HangfireJobActivatorScope(_serviceScopeFactory.CreateScope(), context.BackgroundJob.Id);
  19.     }
  20. }
复制代码
第二步

配置 Hangfire 服务,使其支持 Redis,并且配置一些参数。
  1. private void ConfigureHangfire(IServiceCollection services)
  2. {
  3.         var options =
  4.                 new RedisStorageOptions
  5.                 {
  6.             // 配置 redis 前缀,每个任务实例都会创建一个 key
  7.                         Prefix = "aaa:aaa:hangfire",
  8.                 };
  9.         services.AddHangfire(
  10.                 config =>
  11.                 {
  12.                         config.UseRedisStorage("{redis连接字符串}", options)
  13.                         .SetDataCompatibilityLevel(CompatibilityLevel.Version_180)
  14.                         .UseSimpleAssemblyNameTypeSerializer()
  15.                         .UseRecommendedSerializerSettings();
  16.                         config.UseActivator(new HangfireActivator(services.BuildServiceProvider().GetRequiredService<IServiceScopeFactory>()));
  17.                 });
  18.         services.AddHangfireServer(options =>
  19.         {
  20.         // 注意,这里必须设置非常小的间隔
  21.                 options.SchedulePollingInterval = TimeSpan.FromSeconds(1);
  22.         
  23.         // 如果考虑到后续任务比较多,则需要调大此参数
  24.                 options.WorkerCount = 50;
  25.         });
  26. }
复制代码
4.png

第三步

实现 RecurringJobHandler 执行定时任务,发起 http 请求业务系统。
被调用方要返回 TaskInterfaceResponse 类型,主要考虑如果被调用方后续不需要在继续此定时任务,那么返回参数 CancelTask = tre 时,定时任务服务直接取消后续的任务即可,不需要被调用方手动调用接口取消。
  1. public class RecurringJobHandler
  2. {
  3.     private readonly IServiceProvider _serviceProvider;
  4.     public RecurringJobHandler(IServiceProvider serviceProvider)
  5.     {
  6.         _serviceProvider = serviceProvider;
  7.     }
  8.     /// <summary>
  9.     /// 执行任务.
  10.     /// </summary>
  11.     /// <param name="taskRequest"></param>
  12.     /// <returns>Task.</returns>
  13.     public async Task Handler(TaskRequest taskRequest)
  14.     {
  15.         var ioc = _serviceProvider;
  16.         var recurringJobManager = ioc.GetRequiredService<IRecurringJobManager>();
  17.         var httpClientFactory = ioc.GetRequiredService<IHttpClientFactory>();
  18.         var logger = ioc.GetRequiredService<ILogger<RecurringJobHandler>>();
  19.         using var httpClient = httpClientFactory.CreateClient(taskRequest.ServiceName);
  20.         // 无论是否请求成功,都算完成了本次任务
  21.         try
  22.         {
  23.             // 请求子系统的接口
  24.             var response = await httpClient.PostAsJsonAsync(taskRequest.ServiceName, taskRequest);
  25.             var execteResult = await response.Content.ReadFromJsonAsync<ExecteTasResult>();
  26.             // 被调用方要求取消任务
  27.             if (execteResult != null && execteResult.CancelTask)
  28.             {
  29.                 recurringJobManager.RemoveIfExists(taskRequest.TaskId);
  30.             }
  31.         }
  32.         catch (Exception ex)
  33.         {
  34.             logger.LogError(ex, "Task error.");
  35.         }
  36.     }
  37. }
复制代码
第四步

配置好 Hangfire 后,开始考虑如何接收任务和发起请求,首先定义一个 Http 接口或 grpc 接口。
  1. [ApiController]
  2. [Route("/execute")]
  3. public class HangfireController : ControllerBase
  4. {
  5.     private readonly IRecurringJobManager _recurringJobManager;
  6.     public HangfireController(IRecurringJobManager recurringJobManager)
  7.     {
  8.         _recurringJobManager = recurringJobManager;
  9.     }
  10.     [HttpPost("addtask")]
  11.     public async Task<TaskResponse> AddTask(TaskRequest value)
  12.     {
  13.         await Task.CompletedTask;
  14.         _recurringJobManager.AddOrUpdate<RecurringJobHandler>(
  15.             value.TaskId,
  16.             task => task.Handler(value),
  17.             cronExpression: value.CronExpression,
  18.             options: new RecurringJobOptions
  19.             {
  20.             });
  21.         return new TaskResponse {  };
  22.     }
  23.     [HttpPost("cancel")]
  24.     public async Task<TaskResponse> Cancel(CancelTaskRequest value)
  25.     {
  26.         await Task.CompletedTask;
  27.         _recurringJobManager.RemoveIfExists(value.TaskId);
  28.         return new TaskResponse
  29.         {
  30.         };
  31.     }
  32. }
复制代码
业务服务实现动态代码

业务服务只需要暴露一个 exceute 接口给 HangfireServer 即可,DemoApi 将 Command 序列化包装为请求参数给 HangfireServer ,然后 HangfireServer 原封不动地将参数请求到 exceute 接口。
5.png


对 DemoApi 主要设计过程如下:

  • 定义 SendHangfireService 服务,包装 Command 数据和一些定时任务参数,通过 http 发送到 HangfireServer 中;
  • 定义 ExecuteTaskHandler ,当接口被触发时,实现反序列化参数并使用 MediatR 发送 Command,实现动态执行;
  • 定义 ExecuteController 接口,接收 HangfireServer 请求,并调用 ExecuteTaskHandler 处理请求;

DemoApi 引入类库如下-:
  1. [/code][indent]Maomi.Core 是一个模块化和自动服务注册框架。
  2. [/indent]
  3. [size=3]第一步[/size]
  4. 定义 SendHangfireService 服务,包装 Command 数据和一些定时任务参数,通过 http 发送到 HangfireServer 中。
  5. 接收 HangfireServer 请求时,需要通过字符串查找出 Type,这就需要 DemoApi 启动时,自动扫描程序集并将对应的类型缓存起来。
  6. 为了将定时任务命令和其它 Command 区分处理,需要定义一个统一的抽象,当然也可以不这样做,也可以通过特性注解的方式做处理。
  7. [code]/// <summary>
  8. /// 定时任务抽象参数.
  9. /// </summary>
  10. public abstract class HangfireRequest : IRequest<HangfireResponse>
  11. {
  12.     /// <summary>
  13.     /// 定时任务 id.
  14.     /// </summary>
  15.     public string TaskId { get; init; } = string.Empty;
  16.     /// <summary>
  17.     /// 该任务创建时间.
  18.     /// </summary>
  19.     public DateTimeOffset CreateTime { get; init; }
  20. }
复制代码
定义 HangireTypeFactory ,以便能够通过字符串快速查找 Type。
  1. /// <summary>
  2. /// 记录 CQRS 中的命令类型,以便能够通过字符串快速查找 Type.
  3. /// </summary>
  4. public class HangireTypeFactory
  5. {
  6.     private readonly ConcurrentDictionary<string, Type> _typeDictionary;
  7.     public HangireTypeFactory()
  8.     {
  9.         _typeDictionary = new ConcurrentDictionary<string, Type>();
  10.     }
  11.     public void Add(Type type)
  12.     {
  13.         if (!_typeDictionary.ContainsKey(type.Name))
  14.         {
  15.             _typeDictionary[type.Name] = type;
  16.         }
  17.     }
  18.     public Type? Get(string typeName)
  19.     {
  20.         if (_typeDictionary.TryGetValue(typeName, out var type))
  21.         {
  22.             return type;
  23.         }
  24.         return _typeDictionary.FirstOrDefault(x => x.Value.FullName == typeName).Value;
  25.     }
  26. }
复制代码
最后实现 SendHangfireService 服务,能够包装参数发送到 HangfireServer 中。
当然,可以使用 CQRS 处理。
  1. /// <summary>
  2. /// 定时任务服务,用于发送定时任务请求.
  3. /// </summary>
  4. [InjectOnScoped]
  5. public class SendHangfireService
  6. {
  7.     private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
  8.     {
  9.         AllowTrailingCommas = true,
  10.         PropertyNameCaseInsensitive = true,
  11.         PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
  12.         ReadCommentHandling = JsonCommentHandling.Skip
  13.     };
  14.     private readonly IHttpClientFactory _httpClientFactory;
  15.     public SendHangfireService(IHttpClientFactory httpClientFactory)
  16.     {
  17.         _httpClientFactory = httpClientFactory;
  18.     }
  19.     /// <summary>
  20.     /// 发送定时任务请求.
  21.     /// </summary>
  22.     /// <typeparam name="TCommand"></typeparam>
  23.     /// <param name="request"></param>
  24.     /// <param name="cancellationToken"></param>
  25.     /// <returns></returns>
  26.     /// <exception cref="TypeLoadException"></exception>
  27.     public async Task Send<TCommand>(TCommand request)
  28.         where TCommand : HangfireRequest
  29.     {
  30.         using var httpClient = _httpClientFactory.CreateClient();
  31.         var taskRequest = new TaskRequest
  32.         {
  33.             TaskId = request.TaskId,
  34.             CommandBody = JsonSerializer.Serialize(request, JsonOptions),
  35.             ServiceName = "http://127.0.0.1:5000/hangfire/execute",
  36.             CommandType = typeof(TCommand).Name ?? throw new TypeLoadException(typeof(TCommand).Name),
  37.             CreateTime = request.CreateTime.ToUnixTimeMilliseconds().ToString(),
  38.             CronExpression = request.CronExpression,
  39.         };
  40.         _ = await httpClient.PostAsJsonAsync("http://127.0.0.1:5001/execute/addtask", taskRequest);
  41.     }
  42.     /// <summary>
  43.     /// 取消定时任务.
  44.     /// </summary>
  45.     /// <param name="taskId"></param>
  46.     /// <returns></returns>
  47.     public async Task Cancel(string taskId)
  48.     {
  49.         using var httpClient = _httpClientFactory.CreateClient();
  50.         _ = await httpClient.PostAsJsonAsync("http://127.0.0.1:5001/hangfire/cancel", new CancelTaskRequest
  51.         {
  52.             TaskId = taskId
  53.         });
  54.     }
  55. }
复制代码
第二步

要实现通过 Type 动态执行某个 Command ,其实思路比较简单,也并不需要表达式树等麻烦的方式。
笔者的实现思路如下,定义 ExecuteTaskHandler 泛型类,直接以强类型的方式触发 Command,但是为了屏蔽泛型类型强类型在代码调用中的麻烦,需要再抽象一个接口  IHangfireTaskHandler 屏蔽泛型。
  1. /// <summary>
  2. /// 定义执行任务的抽象,便于忽略泛型处理.
  3. /// </summary>
  4. public interface IHangfireTaskHandler
  5. {
  6.     /// <summary>
  7.     /// 执行任务.
  8.     /// </summary>
  9.     /// <param name="taskRequest"></param>
  10.     /// <returns></returns>
  11.     Task<ExecteTasResult> Handler(TaskRequest taskRequest);
  12. }
复制代码
  1. /// <summary>
  2. /// 用于反序列化参数并发送 Command.
  3. /// </summary>
  4. /// <typeparam name="TCommand">命令.</typeparam>
  5. public class ExecuteTaskHandler<TCommand> : IHangfireTaskHandler
  6.     where TCommand : HangfireRequest, IRequest<ExecteTasResult>
  7. {
  8.     private readonly IMediator _mediator;
  9.     /// <summary>
  10.     /// Initializes a new instance of the <see cref="ExecuteTaskHandler{TCommand}"/> class.
  11.     /// </summary>
  12.     /// <param name="mediator"></param>
  13.     public ExecuteTaskHandler(IMediator mediator)
  14.     {
  15.         _mediator = mediator;
  16.     }
  17.     private static readonly JsonSerializerOptions JsonSerializerOptions = new JsonSerializerOptions
  18.     {
  19.         AllowTrailingCommas = true,
  20.         PropertyNameCaseInsensitive = true,
  21.         PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
  22.         ReadCommentHandling = JsonCommentHandling.Skip
  23.     };
  24.     /// <inheritdoc/>
  25.     public async Task<ExecteTasResult> Handler(TaskRequest taskRequest)
  26.     {
  27.         var command = JsonSerializer.Deserialize<TCommand>(taskRequest.CommandBody, JsonSerializerOptions)!;
  28.         if (command == null)
  29.         {
  30.             throw new Exception("解析命令参数失败");
  31.         }
  32.         // 处理命令的逻辑
  33.         var response = await _mediator.Send(command);
  34.         return response;
  35.     }
  36. }
复制代码
第三步

实现定时任务 execute 触发接口,然后将参数转发到 ExecuteTaskHandler 中,这里通过依赖注入的方式屏蔽和解决强类型的问题。
  1. /// <summary>
  2. /// 定时任务触发入口.
  3. /// </summary>
  4. [ApiController]
  5. [Route("/hangfire")]
  6. public class ExecuteController : ControllerBase
  7. {
  8.     private readonly IServiceProvider _serviceProvider;
  9.     private readonly HangireTypeFactory _hangireTypeFactory;
  10.     public ExecuteController(IServiceProvider serviceProvider, HangireTypeFactory hangireTypeFactory)
  11.     {
  12.         _serviceProvider = serviceProvider;
  13.         _hangireTypeFactory = hangireTypeFactory;
  14.     }
  15.     [HttpPost("execute")]
  16.     public async Task<ExecteTasResult> ExecuteTask([FromBody] TaskRequest request)
  17.     {
  18.         var commandType = _hangireTypeFactory.Get(request.CommandType);
  19.         // 找不到该事件类型,取消后续事件执行
  20.         if (commandType == null)
  21.         {
  22.             return new ExecteTasResult
  23.             {
  24.                 CancelTask = true
  25.             };
  26.         }
  27.         var commandTypeHandler = typeof(ExecuteTaskHandler<>).MakeGenericType(commandType);
  28.         var handler = _serviceProvider.GetService(commandTypeHandler) as IHangfireTaskHandler;
  29.         if(handler == null)
  30.         {
  31.             return new ExecteTasResult
  32.             {
  33.                 CancelTask = true
  34.             };
  35.         }
  36.         return await handler.Handler(request);
  37.     }
  38. }
复制代码
第四步

封装好代码后,开始最后一个环境,配置和注册服务,由于笔者使用 Maomi.Core 框架,因此服务注册配置和扫描程序集变得非常简单,只需要通过 Maomi.Core 框架提供的接口即可最简单地实现功能。
  1. public class ApiModule : Maomi.ModuleCore, IModule
  2. {
  3.     private readonly HangireTypeFactory _hangireTypeFactory;
  4.     public ApiModule()
  5.     {
  6.         _hangireTypeFactory = new HangireTypeFactory();
  7.     }
  8.     public override void ConfigureServices(ServiceContext context)
  9.     {
  10.         context.Services.AddTransient(typeof(ExecuteTaskHandler<>));
  11.         context.Services.AddSingleton(_hangireTypeFactory);
  12.         context.Services.AddHttpClient();
  13.         context.Services.AddMediatR(o =>
  14.         {
  15.             o.RegisterServicesFromAssemblies(context.Modules.Select(x => x.Assembly).ToArray());
  16.         });
  17.     }
  18.     public override void TypeFilter(Type type)
  19.     {
  20.         if (!type.IsClass || type.IsAbstract)
  21.         {
  22.             return;
  23.         }
  24.         if (type.IsAssignableTo(typeof(HangfireRequest)))
  25.         {
  26.             _hangireTypeFactory.Add(type);
  27.         }
  28.     }
  29. }
复制代码
6.png

第五步

开发者可以这样写定时任务 Command 以及执行器,然后通过接口触发定时任务。
  1. public class MyTestRequest : HangfireRequest, IRequest<ExecteTasResult>
  2. {
  3. }
  4. /// <summary>
  5. /// 要被定时任务执行的代码.
  6. /// </summary>
  7. public class MyTestHandler : IRequestHandler<MyTestRequest, ExecteTasResult>
  8. {
  9.     private static volatile int _count;
  10.     private static DateTimeOffset _lastTime;
  11.     public async Task<ExecteTasResult> Handle(MyTestRequest request, CancellationToken cancellationToken)
  12.     {
  13.         _count++;
  14.         if (_lastTime == default)
  15.         {
  16.             _lastTime = DateTimeOffset.Now;
  17.         }
  18.         Console.WriteLine($"""
  19.             执行时间:{DateTimeOffset.Now.ToString("HH:mm:ss.ffff")}
  20.             执行频率(每 10s):{(_count / (DateTimeOffset.Now - _lastTime).TotalSeconds * 10)}
  21.             """);
  22.         return new ExecteTasResult
  23.         {
  24.             CancelTask = false
  25.         };
  26.     }
  27. }
复制代码
  1. [ApiController]
  2. [Route("/test")]
  3. public class SendTaskController : ControllerBase
  4. {
  5.     private readonly SendHangfireService _hangfireService;
  6.     public SendTaskController(SendHangfireService hangfireService)
  7.     {
  8.         _hangfireService = hangfireService;
  9.     }
  10.     [HttpGet("aaa")]
  11.     public async Task<string> SendAsync()
  12.     {
  13.         await _hangfireService.Send(new MyTestRequest
  14.         {
  15.             CreateTime = DateTimeOffset.Now,
  16.             CronExpression = "* * * * * *",
  17.             TaskId = Guid.NewGuid().ToString(),
  18.         });
  19.         return "aaa";
  20.     }
  21. }
复制代码
最后

启动项目测试代码,记录执行频率和时间间隔。
7.png

8.gif


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册