需求:基于Go标准库实现可测试的定时调度函数
使用Go标准库实现带停止机制的定时任务
核心思路
用context.Context传递取消信号实现干净停止,结合time包处理调度逻辑,解析cron表达式计算下一次执行时间,循环执行任务直到收到停止信号。完全基于标准库,无第三方依赖,适配测试场景的快速启停需求。
完整实现代码
package main import ( "context" "fmt" "time" ) // RunSchedule 带停止机制的定时任务调度函数,与原gocron实现行为对齐(立即执行一次+按cron调度) func RunSchedule(ctx context.Context, cronExpression string, task func()) error { // 立即执行一次任务 task() // 解析cron表达式,获取下一次执行时间 nextRun, err := parseCron(cronExpression, time.Now()) if err != nil { return fmt.Errorf("解析cron表达式失败: %w", err) } for { select { case <-ctx.Done(): // 收到停止信号,退出循环 return nil case <-time.After(time.Until(nextRun)): // 执行任务 task() // 计算下一次执行时间 nextRun, err = parseCron(cronExpression, time.Now()) if err != nil { return fmt.Errorf("重新计算执行时间失败: %w", err) } } } } // parseCron 简单解析cron表达式(支持分 时 日 月 周,可根据需求扩展) // 示例支持:*/N(间隔N分钟)、固定时间点(如0 12 * * *每天12点) func parseCron(expr string, now time.Time) (time.Time, error) { // 处理"*/5 * * * *":每5分钟执行一次 if expr == "*/5 * * * *" { nextMin := ((now.Minute() / 5) + 1) * 5 nextRun := now.Truncate(time.Hour).Add(time.Duration(nextMin) * time.Minute) if nextRun.Before(now) { nextRun = nextRun.Add(time.Hour) } return nextRun, nil } // 处理"0 12 * * *":每天12点执行 if expr == "0 12 * * *" { nextRun := time.Date(now.Year(), now.Month(), now.Day(), 12, 0, 0, 0, now.Location()) if nextRun.Before(now) { nextRun = nextRun.Add(24 * time.Hour) } return nextRun, nil } // 可扩展支持更多cron表达式规则,如指定多个分钟、工作日等 return time.Time{}, fmt.Errorf("不支持的cron表达式: %s", expr) }
测试场景使用示例
func TestRunSchedule(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() taskCount := 0 task := func() { taskCount++ } // 启动每秒执行一次的任务(需在parseCron中添加对"* * * * * *"的支持) go func() { if err := RunSchedule(ctx, "* * * * * *", task); err != nil { t.Fatal(err) } }() // 验证立即执行一次 time.Sleep(100 * time.Millisecond) if taskCount != 1 { t.Fatal("任务未立即执行") } // 验证定时执行 time.Sleep(1 * time.Second) if taskCount < 2 { t.Fatal("任务未按时调度执行") } // 发送停止信号并验证 cancel() time.Sleep(1 * time.Second) finalCount := taskCount time.Sleep(1 * time.Second) if taskCount != finalCount { t.Fatal("任务在停止后仍继续执行") } }
关键优势
- 干净停止:通过
cancel()函数可随时终止调度器,无资源泄漏,适配测试场景。 - 行为对齐:保留原实现的
StartImmediately特性,启动时立即执行一次任务。 - 可扩展性:
parseCron函数可根据业务需求扩展,支持更复杂的cron表达式规则。
内容的提问来源于stack exchange,提问作者brettjames25
相关产品推荐
相关产品推荐

