You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

需求:基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 14:02:51