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:
- 把DataManager改为单例(可以通过依赖注入注册为单例,或者用静态实例)。
- 给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(); } } }
- 在控制器中注入单例的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
相关产品推荐
相关产品推荐

