【问题】为什么 System.Timers.Timer 更改间隔时间后的第一次触发时间是设定时间的三倍?
【已解决(20220918)】和 Timer 无关,是使用信息窗控件输出方法时的用法不对,没有加 Dispatcher(见最后)。
一、问题和现象
在编写 “Wifi 固定器[1]” 程序时,按如下方式使用了定时器:
//声明; private Timer _Timer = new Timer() { Interval = 1, AutoReset = true }; //设置处理方法; _Timer.Elapsed += new ElapsedEventHandler(TimerHandler); /// <summary> /// 定时器任务 /// </summary> private async void TimerHandler(object source, ElapsedEventArgs e) { if (_Timer.Interval == 1) //如果是第一次执行 { _Timer.Interval = 1000 * Configs.CheckInterval; //设置Interval为想要的间隔时间。 } //刷新连接状态; _profileRadio = GetProfileRadio(_fixedWifiPack); if (_profileRadio.IsConnected) { Console.WriteLine("该 Wifi 已连接,无需操作"); return; } Console.WriteLine($"即将尝试连接【{_fixedWifiPack.Ssid}】..."); bool result = await NativeWifi.ConnectNetworkAsync(_fixedWifiPack.Interface.Id, _fixedWifiPack.ProfileName, _fixedWifiPack.BssType, TimeSpan.FromSeconds(5)); Console.WriteLine($"连接结果:{(result ? "成功" : "失败")}"); } //开启 if (_Timer.Enabled) { Console.WriteLine($"目前监控已处于开启状态,无需重复操作"); return; } _Timer.Start(); Console.WriteLine($"【开启监控成功】检测间隔时间为 {Configs.CheckInterval}s"); //关闭 if (!_Timer.Enabled) { Console.WriteLine($"目前监控已处于关闭状态,无需重复操作"); return; } _Timer.Stop(); _Timer.Interval = 1; Console.WriteLine($"【关闭监控成功】{Environment.NewLine}");
现象:
也就是,Timer 的 Interval 初始以及停止时,都设置为 1,为的是启动的时候能马上触发一次。然后在第一次触发时修改 Interval 为需要的间隔时间,用作后续的触发间隔。然后问题就来了,修改间隔后的那次触发,距离启动时立马触发的那次,间隔时间达到了设定间隔时间的 3 倍,而且每次都是这样。
修改时间间隔的地方加上先停止后启动,问题依旧:
不使用异步方法,问题依旧:
怀疑是和线程池有关系,进而和 CPU 核心数有关,我这个是四核:
使用 毫秒定时器[2] 或 多媒体定时器[3] 也还是同样的现象。
二、网上的一些解决方法
看到网上也有人遇到类似但不完全相同的问题(《System.Timers.Timer 非常不准确》[4]):
可以看到,他是使用了并行计算所以出问题了,但是我这里并没有使用并行计算。
然后网上一个讨论帖(《System.Timers.Timer 为什么会失效??》[5])是这样说的:
因为怀疑计时不准,所以有好多人自己封装调用 winmm.dll 中的“多媒体计时器”来形成自定义的定时器,我尝试了两种(上面提到过),问题还是一样,所以可能他们这种不能解决我遇到的问题。
三、目前的情况及资料
总之,问题还没解决,所以大家有什么想法或方法,还请不吝赐教。
附-参考资料及整理的资料:
1、Wifi 固定器 代码:https://gitee.com/dlgcy/DLGCY_FixedWifi/tree/Blog20220904
2、毫秒定时器(整理版代码):https://gitee.com/dlgcy/dotnetcodes/blob/dlgcy/DotNet.Utilities/%E5%AE%9A%E6%97%B6%E5%99%A8/MillisecondTimer.cs
3、多媒体定时器(原始代码托管):https://gitee.com/dlgcy/dotnetcodes/blob/dlgcy/DotNet.Utilities/%E5%AE%9A%E6%97%B6%E5%99%A8/MultimediaTimer.cs
4、《System.Timers.Timer 非常不准确》:https://qa.1r1g.com/sf/ask/2286140321/
5、《System.Timers.Timer 为什么会失效??》:https://bbs.csdn.net/topics/90487784?list=764574
6、《Timer 计时不准确的解决方案 每次都重新调整,修正误差》(提到多媒体定时器的文章):https://www.cnblogs.com/chucklu/p/4673600.html
7、《Timer 计时不准确的问题及解决方法》(提到毫秒定时器的文章):https://www.cnblogs.com/dehai/p/4347061.html
四、大家的讨论
(2022.09.05)
4.1、东吴大都督
4.2、谣言似山
20220905:
4.3、其它
五、阶段性解决(by @谣言似山)
5.1 在方法开始处添加一句输出
https://gitee.com/dlgcy/DLGCY_FixedWifi/pulls/1 :
昨晚尝试了几种情况,目前怀疑是你 Interval 设置为 1,太短了。
第一次触发还没执行完(async、void)就返回了,又被排了好几个 Event 进了窗口的消息队列。然后这些消息队列里的 event 执行的时候,读取_Timer 的 Interval 又出了什么问题。(仅仅是个猜测)加 Console.ReadLine 可以解决,怀疑是 Console.Readline 耗时较长,解决了 “读取_Timer 的 Interval” 的问题。(图见链接)
空的 Console.Readline 不能解决问题,可能是因为它不向 stdout 里输出东西,执行的太快。(图见链接)
今早看了你的代码,你是为了 “立刻触发一次” 才搞了 “Interval 设置为 1” 的操作。其实大可不必,可以直接调用一次 TimerHandler 的。
这个我也按他说的尝试了一下,确实是可以的,同时测试排除了 Console 重定向的嫌疑:
5.2 放弃使用让 Timer“立即触发一次”的方案
_Timer 的 Interval 设置移到_Timer.Start () 之前,_Timer 的 “立即触发一次” 改为在 _Timer.Start () 之后,手动调用一次 TimerHandler,取消停止 Timer 之后,手动把 Interval 改为 1 的操作。
改动如下:
所以修复后的代码路径为:https://gitee.com/dlgcy/DLGCY_FixedWifi/tree/Blog20220905
感谢感谢!
六、断线重连后还是有同样的问题 [已解决]
(2022.09.18)
现象:
当前代码:
尝试解决:
1、加了 Try-Catch,没有发现异常。
2、打印线程号,看看能不能发现什么问题:
发现线程号输出只输出了一次,然后问题还是存在:
看来,问题可能出在信息窗控件里,或者是调用信息窗控件输出的这个方法 ShowInfoUc 有问题。
3、修改绑定基类中的向信息窗控件输出信息的方法(问题解决)
也就是之前相关代码没有在 Dispatcher 中调用,导致在线程中会有问题,看来有点错怪 Timer 了。
已经正常了:
去掉线程输出看看,正常:
修复后的代码:https://gitee.com/dlgcy/DLGCY_FixedWifi/tree/Blog20220918
发表评论