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

API查询参数持久化异常:Timer重复执行旧参数值

问题根源分析

1. 每次请求都会创建独立的Timer实例

你的Get接口每次被调用时,都会new一个全新的DataManager对象,而每个DataManager内部都会初始化自己的Timer。第一个请求的Timer要等60秒((DateTime.Now - TimerStarted).Seconds > 60)才会调用_timer.Dispose(),在这60秒内如果发起第二次请求,就会同时存在两个(甚至更多)活跃的Timer实例,各自按照自己的周期执行任务。

2. 闭包捕获导致参数被固定

你传给DataManager的异步lambda表达式:

async () => await _paymentDraftHub.Clients.All.SendAsync(SignalRConstants.TransferPaymentDraftServiceData, await _paymentTransactionRepository.GetAllDeclinedAsync(pageSize, pageNumber))

这里的pageSize和pageNumber是捕获了当前请求上下文的局部变量。每个DataManager实例的lambda都会绑定到创建它时的那组参数值,所以旧Timer会一直用第一次请求的参数,新Timer用第二次的,导致两者同时执行不同参数的逻辑。


解决方案

针对这个问题,你可以从以下几个方向入手:

方案一:使用单例模式管理DataManager

确保应用中同一时间只有一个DataManager实例在运行,新请求过来时更新参数并重置Timer:

  1. 把DataManager改为单例(可以通过依赖注入注册为单例,或者用静态实例)。
  2. 给DataManager添加更新回调和参数的方法,比如:
public class DataManager { 
    private Timer _timer; 
    private AutoResetEvent _autoResetEvent; 
    private Func<Task> _currentTask; 
    private DateTime _timerStarted;

    public DataManager() { 
        _autoResetEvent = new AutoResetEvent(false); 
    }

    public void UpdateTask(Func<Task> newTask) {
        // 先停止旧Timer
        _timer?.Dispose();
        _currentTask = newTask;
        _timerStarted = DateTime.Now;
        // 启动新Timer
        _timer = new Timer(Execute, _autoResetEvent, 0, 10000);
    }

    private void Execute(object stateInfo) { 
        _currentTask?.Invoke(); 
        if ((DateTime.Now - _timerStarted).Seconds > 60) { 
            _timer.Dispose(); 
        } 
    } 
}
  1. 在控制器中注入单例的DataManager,每次请求时调用UpdateTask方法传入新的参数和任务:
private readonly DataManager _dataManager;

// 构造函数注入
public YourController(DataManager dataManager) {
    _dataManager = dataManager;
}

[HttpGet] 
public async Task<IActionResult> Get([FromQuery] PagedTransactionDataRequest queryParams) { 
    var pageSize = queryParams.PageSize ?? 1; 
    var pageNumber = queryParams.PageNumber ?? 10; 

    _dataManager.UpdateTask(async () => 
        await _paymentDraftHub.Clients.All.SendAsync(
            SignalRConstants.TransferPaymentDraftServiceData, 
            await _paymentTransactionRepository.GetAllDeclinedAsync(pageSize, pageNumber)
        )
    );

    var response = new ResponseMessage { Message = "Accepted", Code = "201" }; 
    return Ok(response); 
}

方案二:跟踪并清理旧的Timer实例

如果不需要单例,也可以在控制器中用静态变量存储当前活跃的DataManager实例,每次请求时先销毁旧实例再创建新的:

private static DataManager _activeDataManager;
private static readonly object _lockObj = new object();

[HttpGet] 
public async Task<IActionResult> Get([FromQuery] PagedTransactionDataRequest queryParams) { 
    var pageSize = queryParams.PageSize ?? 1; 
    var pageNumber = queryParams.PageNumber ?? 10; 

    // 线程安全地清理旧Timer
    lock(_lockObj)
    {
        _activeDataManager?._timer.Dispose();
        _activeDataManager = new DataManager(async () => 
            await _paymentDraftHub.Clients.All.SendAsync(
                SignalRConstants.TransferPaymentDraftServiceData, 
                await _paymentTransactionRepository.GetAllDeclinedAsync(pageSize, pageNumber)
            )
        );
    }

    var response = new ResponseMessage { Message = "Accepted", Code = "201" }; 
    return Ok(response); 
}

注意:必须加锁保证线程安全,避免多请求同时操作导致的异常。


额外建议

  • 考虑给Timer的生命周期添加更明确的控制逻辑,比如允许前端主动停止推送,或者在没有活跃客户端时自动停止Timer。
  • 闭包捕获变量时要注意变量的生命周期,避免意外的引用导致内存泄漏或逻辑错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 06:54:51