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

C#微定时器任务超期并行执行疑问及规避方案咨询

问题与解决方案

问题描述

我在C#脚本中使用一款微定时器DLL,要求某函数在20秒内严格按每50ms执行一次。但受网络影响,该函数有时执行耗时会超过50ms。

我给微定时器的回调函数加了计数器,20秒后如果计数器值为400,就认为所有执行都按时完成;但有时计数器值会大于400,我推测是前一次函数还没执行完,定时器就再次触发,导致函数并行执行,计数器被重复累加了。

现在有两个问题:

  1. 关于计数器值大于400的原因,我的推测是否正确?
  2. 当函数执行耗时超过50ms时,怎么避免前一次还没执行完就触发下一次?如果耗时小于60ms可以容忍,需要在本次执行完成后再启动下一次,同时尽量保持每50ms执行一次的规则。

附当前代码:

public async void OnTimedEvent_MicroTimer(object sender, MicroLibrary.MicroTimerEventArgs timerEventArgs)
{
    object microcount;
    if (Measuring_period_finished == false) // 结束信号来自另一个定时器
    {
        Counter += 1; 
        Periodic_funciton();             
    }

    if (Measuring_period_finished == true) 
    {
        if( Counter > 400)
        { 
            delayed_functions=Counter-400;
            message="function executed "+delayed_functions.toString()+" times delayed";
        }
        if( Counter == 400); 
        {
            message="function executed always in time";
        }
    }    
}

解答

1. 计数器值大于400的原因推测是否正确?

是的,你的推测完全正确。这款微定时器的默认逻辑是不管回调函数是否执行完毕,都会按设定的固定间隔触发下一次回调,属于典型的“基于间隔”触发机制。当Periodic_funciton()执行耗时超过50ms时,下一次定时器触发会直接开启新线程执行回调,导致多次回调并行运行,每次都会执行Counter += 1,最终计数器自然会超过400。

2. 如何避免前一次未完成就触发下一次?

要实现“本次执行完成后再安排下一次”的逻辑,同时尽量贴近50ms的执行间隔,可以按以下方案修改:

核心思路

  • 加一个线程安全的执行锁,标记当前是否正在运行目标函数,避免并行执行;
  • 记录每次执行的结束时间,根据实际耗时调整下一次触发的间隔,尽量补回时间偏差(满足耗时小于60ms的容忍条件)。

修改后的基础版本代码

// 新增线程安全的执行锁,避免并行执行
private readonly object _executionLock = new object();

public void OnTimedEvent_MicroTimer(object sender, MicroLibrary.MicroTimerEventArgs timerEventArgs)
{
    // 尝试获取锁,获取不到说明当前正在执行,直接跳过本次触发
    if (!Monitor.TryEnter(_executionLock))
    {
        return;
    }

    try
    {
        if (!Measuring_period_finished)
        {
            Counter += 1;
            Periodic_funciton();
        }

        if (Measuring_period_finished)
        {
            if (Counter > 400)
            {
                delayed_functions = Counter - 400;
                message = $"函数延迟执行了 {delayed_functions} 次";
            }
            else if (Counter == 400)
            {
                message = "所有函数执行均按时完成";
            }
        }
    }
    finally
    {
        // 确保锁被释放,避免死锁
        Monitor.Exit(_executionLock);
    }
}

进阶优化(贴近50ms间隔)

如果想在任务完成后尽量补回时间偏差,让整体执行节奏更接近50ms间隔,可以在每次执行完成后计算调整下一次的定时器间隔:

// 定时器实例
private MicroLibrary.MicroTimer _microTimer;
// 记录上一次执行的结束时间
private DateTime _lastExecutionEndTime;

// 初始化定时器的方法
public void InitMicroTimer()
{
    _microTimer = new MicroLibrary.MicroTimer();
    _microTimer.Interval = 50; // 初始间隔设为50ms
    _microTimer.MicroTimerElapsed += OnTimedEvent_MicroTimer;
    _lastExecutionEndTime = DateTime.Now;
}

public void OnTimedEvent_MicroTimer(object sender, MicroLibrary.MicroTimerEventArgs timerEventArgs)
{
    if (!Monitor.TryEnter(_executionLock))
    {
        return;
    }

    try
    {
        if (!Measuring_period_finished)
        {
            Counter += 1;
            Periodic_funciton();
            
            DateTime currentEndTime = DateTime.Now;
            // 计算本次执行耗时
            TimeSpan executionCost = currentEndTime - _lastExecutionEndTime;
            
            // 调整下一次间隔:如果耗时小于60ms,补回偏差;否则按50ms重新开始
            int nextInterval = executionCost.TotalMilliseconds < 60 
                ? Math.Max(1, 50 - (int)executionCost.TotalMilliseconds) 
                : 50;
            
            _microTimer.Interval = nextInterval;
            _lastExecutionEndTime = currentEndTime;
        }

        // 后续统计逻辑同基础版本
        if (Measuring_period_finished)
        {
            if (Counter > 400)
            {
                delayed_functions = Counter - 400;
                message = $"函数延迟执行了 {delayed_functions} 次";
            }
            else if (Counter == 400)
            {
                message = "所有函数执行均按时完成";
            }
        }
    }
    finally
    {
        Monitor.Exit(_executionLock);
    }
}

注意事项

  • 原代码中async void的回调可能引发线程上下文混乱,建议改为同步逻辑,或者确认定时器支持async Task类型的回调;
  • 如果需要允许任务排队(而非跳过),可以用SemaphoreSlim替代Monitor,但要注意避免任务堆积导致内存溢出;
  • 代码中Math.Max(1, ...)是为了避免间隔设为0导致定时器异常。

内容的提问来源于stack exchange,提问作者Mustafa Levrek

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 17:42:46