时间间隔与条数优先触发的批处理任务

博客介绍了高性能批处理代码,当数据量或时间达到一定条件会触发任务,完成后再判断。触发条件有时间触发和条数触发,每次触发后会清空计时和计数。重新计时有强制精确计时和有一定误差计时两种方式,并分别给出了实现思路和代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 高性能批处理代码

 当数据量达到一定数值或者时间达到一定间隔,任何一个满足条件,都将触发任务,任务完成之后再进行下一次条件判断,(能满足海量数据的批量传输和少量数据的较低延迟传输)

 条件1: 当时间达到了一定间隔 称为时间触发(自定义名称,方便理解后续代码)

 条件2: 当条数达到了一定数量,称为条数触发(自定义名称,方便理解后续代码)

 每一次触发计算后都会清空条件1的计时(重新计时),同时也会清空条件2的数量(重新计数)

核心思路

1.每次添加数据都判断一次是否达到了批处理的最大数据量

2.开启一个循环定时任务 每次到达规定时间间隔(时间触发)就检查是否需要批处理(两次时间触发之间没有出现条数触发就需要执行时间触发)

3.在执行批处理的时候 不允许添加新的数据,所以在执行批处理方法与添加数据的方法中要添加相同的当前对象锁this

代码如下

import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.function.Consumer;

/**
 * 批处理类
 * 时间间隔或数据量两个条件满足任何一个即可触发任务
 * 往对象中添加数据,达到上面所述的任何一个条件时,会触发任务,之后自动重置条件并清空缓存添加的数据
 */
@Slf4j
@Data
public class BatchCommon<T> {

    private List<T> dataList;//存储添加的数据
    private int maxCount;//最大批处理数据条数
    private String jobName;//任务名称
    private long waitMilliSecond;//最大等待时间
    private Consumer<List<T>> consumer;//自定义函数 内部用来处理批次的数据
    private boolean lastJobIsMaxCount;//上一次是否是时间触发
    private boolean closeTimeJob;//是否需要关闭时间触发任务
    private long sum = 0;//累计处理数据量
    private final Object obj = new Object();//累计处理数据量

    /**
     * 默认初始化
     *
     * @param jobName         任务名称
     * @param maxCount        最大批处理数据条数
     * @param waitMilliSecond 时间触发间隔 毫秒
     * @param consumer        自定义处理方法
     */
    public BatchCommon(String jobName, int maxCount, long waitMilliSecond, Consumer<List<T>> consumer) {
        this.jobName = jobName;
        this.maxCount = maxCount;
        this.dataList = new ArrayList<>(maxCount);
        this.waitMilliSecond = waitMilliSecond;
        this.consumer = consumer;
        this.lastJobIsMaxCount = true;
        this.closeTimeJob = false;
        getTimeJob().start();//开启时间触发任务
    }


    /**
     * 时间触发任务
     *
     * @return 时间触发任务对象
     */
    public Thread getTimeJob() {
        return new Thread(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                while (true) {
                    Thread.sleep(waitMilliSecond);//休眠
                    synchronized (obj) {
                        if (closeTimeJob && dataList.isEmpty()) {
                            log.info("退出: {}, 累计处理数据量: {}", jobName, sum);
                            break;
                        }
                        if (dataList.isEmpty()) continue;
                        if (lastJobIsMaxCount) {//只有中途没有出现条数触发,则执行时间触发,如果中途出现了一次条数触发,跳过当前的时间触发
                            exec("时间触发");
                        } else {
                            lastJobIsMaxCount = true;
                        }
                    }
                }
            }
        });
    }

    /**
     * 添加数据
     *
     * @param data 被添加的数据
     */
    public void add(T data) {
        synchronized (obj) {
            dataList.add(data);
            if (dataList.size() == this.maxCount) {
                lastJobIsMaxCount = false;//置换为条数触发
                exec("数量触发");
            }
        }
    }

    /**
     * 执行批处理任务
     *
     * @param execType 批处理的触发方式
     */
    @SneakyThrows
    private void exec(String execType) {
        log.info("任务: {}, 类型: {} 数据量: {}", jobName, execType, dataList.size());
        consumer.accept(dataList);
        sum += dataList.size();
        dataList.clear();
    }

    /**
     * 关闭任务,需要安全关闭 所以这里设置的是状态,在线程里面检查状态后主动退出
     */
    public void close() {
        synchronized (this) {
            closeTimeJob = true;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Consumer<List<String>> consumer = list -> {
            System.out.println("处理当前一批数据的方法体" + list.size());
        };
        BatchCommon<String> batchCommon1 = new BatchCommon<>("任务1", 3, 5000, consumer);
        BatchCommon<String> batchCommon2 = new BatchCommon<>("任务2", 3, 5000, consumer);
        BatchCommon<String> batchCommon3 = new BatchCommon<>("任务3", 3, 5000, consumer);
        for (int i = 0; i < 200; i++) {
            new Thread(() -> batchCommon1.add("")).start();
            new Thread(() -> batchCommon2.add("")).start();
            new Thread(() -> batchCommon3.add("")).start();
            Thread.sleep(Math.round(Math.random() * 200));
        }
        batchCommon1.close();//执行完成后,退出任务 需要关闭时间触发的循环任务
        batchCommon2.close();
        batchCommon3.close();

    }
}

<think>我们正在排查Windows任务计划程序未按预期时间执行任务的问题。结合用户提供的引用和之前的问题,我们可以从多个角度进行排查。引用[1]提到需要启动任务调度循环,这提醒我们在程序内部是否有正确的循环机制(如果是脚本任务)。引用[2]强调了测试计划任务的重要性和方法,因此我们需要手动测试任务并检查历史记录。引用[3]介绍了周期性任务调度算法,但我们的问题更可能是配置问题而非算法问题。引用[4]提供了系统排查启动项和计划任务的方法,包括使用命令行检查。我们将从以下几个方面进行排查:1.确认任务计划程序的配置正确(包括触发器设置、任务启用状态等)。2.检查程序或脚本本身是否包含循环调度机制(如引用[1]所述,如果是Python脚本使用schedule模块,则需要循环)。3.检查系统日志和任务历史记录,以获取错误信息。4.检查权限和账户设置。5.检查是否有外部因素阻止任务运行(如系统休眠、电池设置等)。详细步骤:一、基础配置检查-确认任务已启用:在任务计划程序库中,右键任务->启用(如果已启用则忽略)。-检查触发器设置:双击任务,查看触发器选项卡,确保触发器的时间、频率设置正确,并且没有过期。-检查条件设置:在“条件”选项卡中,确保没有勾选“仅当计算机使用交流电源时运行”(对于笔记本电脑,如果使用电池可能不会运行)。同时,检查是否勾选了“如果计算机使用电池供电,则停止任务”。-检查设置选项卡:在“设置”选项卡中,确保“如果任务失败,按以下频率重新启动”已配置,并且“如果任务运行时间超过以下时间,则停止任务”的时间设置合理。二、程序内部逻辑检查(针对脚本任务)-如果任务是运行一个脚本(如Python脚本),请确认脚本中是否包含了持续运行的循环(如引用[1]中的`whileTrue`循环)。如果没有,脚本执行一次就会退出,不会在计划的时间再次运行。-对于周期性任务任务计划程序的触发器设置应该能够触发每次运行,但前提是程序能够正常退出以便下次触发。如果程序一直运行不退出,可能导致下次触发时因为前一个实例还在运行而被跳过(除非设置了允许多个实例)。三、历史记录错误日志-在任务计划程序中,选中任务,然后在右侧操作面板中点击“启用所有任务历史记录”(如果尚未启用)。-查看任务的历史记录:在“历史记录”选项卡中,查看最近的运行记录。注意事件级别为“错误”的记录,查看错误代码和描述。-在Windows事件查看器中,查看“应用程序和服务日志”->“Microsoft”->“Windows”->“TaskScheduler”下的日志。四、权限和账户-在“常规”选项卡中,检查运行任务的账户是否正确。如果使用特定用户账户,确保该账户密码未更改(如果更改需要重新输入密码)。如果使用SYSTEM账户,注意该账户可能无法访问网络资源或用户目录。-勾选“使用最高权限运行”。-对于需要桌面交互的程序,如果使用SYSTEM账户则无法显示界面。这时需要选择“不管用户是否登录都要运行”并勾选“不存储密码”(对于需要交互的程序,建议使用用户账户并选择“只在用户登录时运行”)。五、外部因素-系统休眠:如果系统在触发时间处于休眠状态,任务可能不会运行(除非勾选了“唤醒计算机运行此任务”)。-电池设置:对于笔记本电脑,在电池供电时可能不会运行(条件中设置)。-其他:安全软件可能阻止任务运行。六、手动测试和调试-在任务计划程序中,右键任务->运行,手动触发任务,观察是否能够正常运行。-如果手动运行成功,但自动触发不运行,则问题可能出在触发器设置或系统状态(如休眠)。-可以尝试将任务设置为一个更近的时间,观察是否能够触发。七、高级排查-使用命令行检查任务:`schtasks/query/tn"任务名称"/v`,查看任务状态。-使用命令行立即运行任务:`schtasks/run/tn"任务名称"`,看是否成功。-如果任务运行的程序需要管理员权限,确保任务配置了“使用最高权限运行”。八、程序路径和参数-确保在“操作”选项卡中,程序和脚本的路径正确,且参数格式正确。如果路径或参数中包含空格,使用双引号括起来。根据上述排查步骤,用户应该能够定位问题。注意:由于引用[1]是关于Python脚本内部使用schedule模块的,而任务计划程序是一个外部触发器,两者并不冲突。但是,如果用户同时使用了任务计划程序触发和脚本内部循环,需要注意:-任务计划程序触发脚本一次,脚本内部循环会按照自己的时间表运行。这时,任务计划程序只需要触发一次(例如每天触发一次脚本,然后脚本内部每小时运行一次任务)。如果任务计划程序设置的触发频率和脚本内部的循环频率不一致,需要调整。生成相关问题:1.如何手动运行任务计划程序中的任务?2.任务计划程序中的任务历史记录如何查看?3.如何配置任务计划程序以唤醒计算机运行任务?4.在脚本内部循环和任务计划程序触发之间如何选择?</think> ### 排查任务计划程序未按预期执行的详细步骤 结合您的描述和引用内容,以下是系统级排查方案(优先检查关键配置): --- #### 🔍 一、核心配置排查(优先级 ★★★) 1. **验证触发器有效性** - 检查触发器的 **时间/周期** 是否合法(如避免设置过去时间) - 确认触发器的 **状态**:需显示 `已启用`[^2] - **诊断命令**: ```cmd schtasks /query /tn "任务名" /fo list | findstr "触发器" ``` 2. **检查任务启用状态** - 在 `taskschd.msc` 中右键任务 → 确认 **"启用"** 已勾选 - **常见陷阱**:任务被意外禁用后需手动重启服务 `net schedule start`[^4] 3. **排查依赖循环机制(针对脚本任务)** 若任务调用含定时逻辑的脚本(如 Python 的 `schedule` 模块): ```python while True: # ✅ 必须存在持续运行的循环 schedule.run_pending() time.sleep(1) # 建议1-10秒间隔[^1] ``` ⚠️ 缺少此循环会导致脚本执行后立即退出。 --- #### ⚙️ 二、执行环境排查(优先级 ★★) 1. **程序路径参数验证** - 绝对路径规则:`"C:\Path\app.exe" --param=value`(空格路径需加引号) - 批处理需完整路径:`C:\Scripts\task.bat`[^2] 2. **账户权限控制** | 账户类型 | 限制 | 解决方案 | |---------|------|---------| | `SYSTEM` | 无法访问网络资源 | 改用特权用户账户[^4] | | 普通用户 | 缺少文件权限 | 勾选 **"使用最高权限运行"** | ```cmd schtasks /change /tn "任务名" /ru "Domain\User" /rp "密码" ``` 3. **工作目录变量** - 在 **"操作"** → **"起始于"** 设置工作目录(如 `C:\App\`) - 使用脚本显式声明变量: ```bat @echo off SET PATH=C:\Tools;%PATH% # 覆盖系统PATH[^1] start program.exe ``` --- #### 📊 三、系统级深度检测(优先级 ★★) 1. **日志关联分析** - **任务历史**:任务计划程序 → 选中任务 → **"历史记录"** 选项卡 - **系统日志**:事件查看器 → `Windows 日志 → 系统`(筛选 `TaskScheduler` 来源) - **错误码解析**: ```math 0x80070005 = \text{权限不足} \quad 0x80070002 = \text{文件未找到} ``` 2. **外部拦截检测** | 工具 | 检测目标 | 命令 | |------|---------|------| | `msinfo32` | 冲突启动项 | 开始菜单运行 `msinfo32`[^4] | | `services.msc` | 异常服务 | 检查非 `Microsoft` 服务状态 | | 安全软件 | 执行拦截 | 临时禁用防火墙/杀毒软件测试 | --- #### 🔄 四、高级场景处理 1. **处理周期性任务偏差** - **简单周期算法**:上次执行完成后再计划下次(可能累积延迟) - **固定间隔算法**:需在任务 **"设置"** 中勾选: ``` ☑ 如果任务未按计划运行,立即运行 ☑ 如果任务运行时间超过: 重启任务 ```[^3] 2. **休眠唤醒兼容性** 在 **"条件"** 选项卡启用: ``` ☑ 唤醒计算机运行此任务 ☐ 仅当计算机使用交流电源时运行 # 笔记本需取消[^1] ``` --- ```mermaid graph TD A[任务未执行] --> B{配置检查} B --> C[触发器有效性] B --> D[任务启用状态] A --> E{执行环境} E --> F[路径/参数规范] E --> G[账户权限] E --> H[工作目录] A --> I{系统干扰} I --> J[日志分析] I --> K[服务/启动项冲突] I --> L[安全软件拦截] ```
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小钻风巡山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值