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

C#定时器场景下事件处理器并行执行及定时器策略合理性问询

舞台灯光自动化控制系统定时器与并行执行问题

背景

我正在研发一套舞台/演唱会自动化灯光控制系统。简单来说,DMX协议每秒约40次通过线路发送512字节数据,映射到特定字节(通道)的接收灯具会据此调整状态——比如调光器在7通道、值为255时灯具全亮,值为0时熄灭。计算机与DMX接口间通过Art-Net、sACN等网络协议通信,我的代码需要每25ms(每秒40次)向网络发送对应格式的数据包。

发送字节数据的操作难度不高,字节数组打包开销极低,但有大量操作会修改这些数组的值:比如屏幕手动推子输入、灯光淡入淡出或移动算法等。我已经构思了优先级处理逻辑,当前更关注如何让这些操作在刷新周期内完成——每个操作单独执行仅需1-2毫秒,但必须全部在下一次刷新前完成。

当前定时器实现代码

_sendingTimer = new System.Timers.Timer();
_sendingTimer.Interval = _networkInterface.SendingInterval; // 25ms
_sendingTimer.AutoReset = false;
_sendingTimer.Elapsed += async (_, _) =>
{
    SendTheData(); // 平均耗时1-2毫秒

    _stateManager.OnUpdateCycle();
    
    _sendingTimer.Start();
};
_sendingTimer.Start();

StateManager相关代码

public void OnUpdateCycle()
{
    UpdateCycled.Invoke(this, EventArgs.Empty); // 这些处理器能否并行执行?
}
public event EventHandler UpdateCycled;

UpdateCycled事件会绑定大量处理器,主要执行灯光交叉淡入淡出调光值计算这类轻量化数学运算,开启调试或日志时单处理器最多耗时2毫秒。但如果绑定25个处理器,串行执行可能无法在25ms周期内完成。

问题

  1. 当前定时器设置能否保证以合理精度按间隔执行?若方法执行超时,能否自动放弃上一次任务?理想情况是超时则放弃前一次执行。
  2. 有没有高效方式让事件处理器并行执行,且充分利用资源确保完成?由于采用UDP协议发送数据包,丢包不影响后续灯具响应,但事件处理器计算未完成会导致多个周期的间隙,使淡入淡出或移动效果不流畅(部分效果遵循正弦曲线,卡顿会很明显)。(假设计算值基于时间而非上一次值)

解答

问题1解答

当前的定时器设置无法保证严格的25ms间隔精度,也不会自动放弃超时的任务:

  • System.Timers.Timer的精度依赖系统时钟(通常是10-15ms的分辨率),加上回调执行的耗时,实际间隔会有波动。
  • 你的代码是在回调执行完所有逻辑后才重新启动定时器,属于延迟触发模式:如果SendTheData()或OnUpdateCycle()的总耗时超过25ms,下一次触发会直接延后,不会跳过超时的任务。

要实现「超时则放弃前一次任务」的逻辑,需要额外的任务跟踪机制:

private Task _currentUpdateTask = Task.CompletedTask;

_sendingTimer.Elapsed += async (_, _) =>
{
    // 如果上一次任务还在执行,直接跳过
    if (!_currentUpdateTask.IsCompleted)
    {
        _sendingTimer.Start();
        return;
    }

    SendTheData();

    // 用Task.Run包裹更新逻辑,跟踪任务状态
    _currentUpdateTask = Task.Run(() => _stateManager.OnUpdateCycle());
    
    _sendingTimer.Start();
};

这种方式下,如果上一次的OnUpdateCycle()还没执行完,新的周期会直接跳过计算,保证定时器按间隔触发,避免任务堆积。

问题2解答

可以通过并行执行事件处理器来提升效率,同时保证计算在周期内完成:

  1. 并行触发事件处理器
    默认UpdateCycled.Invoke()是串行执行所有绑定的事件处理器,你可以手动遍历事件委托的调用列表,用Parallel.ForEach并行执行:

    public void OnUpdateCycle()
    {
        var handlers = UpdateCycled;
        if (handlers == null) return;
    
        // 并行执行所有事件处理器
        Parallel.ForEach(handlers.GetInvocationList(), handler =>
        {
            ((EventHandler)handler)(this, EventArgs.Empty);
        });
    }
    

    这种方式能利用多核CPU资源,大幅减少总执行时间——25个各耗时2ms的任务,串行需要50ms,并行的话在4核CPU上可能只需要8-10ms(考虑线程调度开销)。

  2. 额外优化建议

    • 提前计算时间戳:因为你的计算基于时间而非上一次值,在周期开始时就获取当前时间戳,所有处理器共用这个时间戳,避免因执行顺序导致的时间偏差。
    • 限制并行度:如果CPU核心数有限,可以通过ParallelOptions限制最大并行数,避免线程调度开销过大:
      var options = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount };
      Parallel.ForEach(handlers.GetInvocationList(), options, handler =>
      {
          ((EventHandler)handler)(this, EventArgs.Empty);
      });
      
    • 避免锁竞争:如果事件处理器会修改同一个DMX字节数组,必须保证线程安全——可以为每个通道设置独立的锁,或者使用线程安全的数据结构,避免全局锁导致的并行效率下降。

内容的提问来源于stack exchange,提问作者Jeff Putz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 13:17:43