为啥有时在计时器滴答事件中它会连续两次调用该方法?

Posted

技术标签:

【中文标题】为啥有时在计时器滴答事件中它会连续两次调用该方法?【英文标题】:Why sometimes inside the timer tick event it's calling the method twice in a row?为什么有时在计时器滴答事件中它会连续两次调用该方法? 【发布时间】:2016-03-22 11:21:28 【问题描述】:

我有两个项目的解决方案。 在一个库项目中,我添加了一个公共静态布尔变量并将其设置为 true。 然后在 Windows 窗体项目中,我使用了该标志。 在 windows 窗体项目设计器中,我添加了一个计时器,将其间隔设置为 1000。

我正在构造函数中启动计时器。 然后在计时器滴答声中,即使我正在做:

private void timer1_Tick(object sender, EventArgs e)

    if (SDKHandler.Saved == true)
    
        timer1.Stop();
        DisplayLastTakenPhoto();
        TakePhotoButton.Enabled = true;
        SDKHandler.Saved = false;
        timer1.Start();
    

还有 DisplayLastTakenPhoto() 方法

private void DisplayLastTakenPhoto()

    string mypath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "RemotePhoto");
    var directory = new DirectoryInfo(mypath);
    var myFile = directory.EnumerateFiles()
        .Where(f => f.Extension.Equals(".jpg", StringComparison.CurrentCultureIgnoreCase) || f.Extension.Equals("raw", StringComparison.CurrentCultureIgnoreCase))
        .OrderByDescending(f => f.LastWriteTime)
        .First();

    if (WaitForFile(myFile.FullName) == true) LiveViewPicBox.Load(myFile.FullName);

还有 WaitForFile 方法

bool WaitForFile(string fullPath)

    int numTries = 0;
    while (true)
    
        ++numTries;
        try
        
            using (FileStream fs = new FileStream(fullPath, FileMode.Open,  FileAccess.ReadWrite, FileShare.None, 100))
            
                fs.ReadByte();
                break;
            
        
        catch (Exception ex)
        
            if (numTries > 10)
            
                return false;
            
            System.Threading.Thread.Sleep(500);
        
    
    return true;

有时并非一直如此,但在某些情况下,当它调用方法 DisplayLastTakenPhoto();连续两次。即使我先停止计时器,我也在做 timer1.Stop();但在某些情况下,我仍然看到该方法被调用了两次。

第二次它使程序挂起/冻结有时甚至 1-3 秒。

【问题讨论】:

这是什么定时器?它看起来像一个表单计时器? 同样如此。这是我从设计器的工具箱中拖出来的窗体计时器。 在上面下个断点,看看为什么会被调用两次。 【参考方案1】:

您所看到的完全正常。那一秒真的很快就结束了,尤其是在调试时。然后您的代码将尝试再次加载相同的图像文件。除了这第二次您的 WaitForFile() 将失败并挂起您的 UI 5 秒钟。由于文件已锁定,因此 PictureBox.Load() 将锁定图像文件。由 Image 类使用的内存映射文件生成,这是一种将图像的像素数据保留在页面文件之外的非常有效的方法。但因产生难以诊断的图像文件操作失败而臭名昭著:)

您需要改进代码,不要尝试两次加载同一个文件。只需使用存储上次使用路径的变量即可完成。并且保持超时更适度,5秒对于挂起UI来说太长了,最多一秒是合理的。另请注意,您根本不需要超时。因为计时器确保您会在一秒钟后重试。

顺便说一句,文件锁定很容易避免,Bitmap(Image) 构造函数可以完成这项工作,它会进行深层复制并允许您处理源图像。但在这种情况下不是解决问题的正确方法。

您可以通过使用 FileSystemWatcher 而不是使用计时器轮询来进一步改进它。但是,您将再次遇到完全相同的锁定问题,在 FSW 事件触发的确切时刻,该文件很少可访问,因为正在写入文件的任何进程仍未关闭它。只要您知道会发生这种情况,这一切都可以解决。

【讨论】:

汉斯我还没有解决它,但我正在尝试。 5 秒超时是什么意思?在我的代码中我在哪里使用 5 秒超时?谢谢。 您的 WaitForFile() 方法调用 Thread.Sleep(500) 十次。不要那样做。根本不要循环,没有必要。 Hans 好的,我完全删除了循环。还删除了不使用 Sleep(500) 并且我还添加了一个检查,因此它不会两次加载相同的文件。仍然有时在使用断点时,我在计时器滴答事件中看到它调用方法 DisplayLastTakenPhoto();连续两次,所以现在有时程序会挂起 1-2 秒,而不是像以前那样挂起 5 秒。我还应该做什么?【参考方案2】:

查看MSDN documentation中的备注和注释。这很可能会导致您观察到的行为:

在您通过调用 Stop 禁用 Timer 后调用 Start 将导致 Timer 重新启动中断的间隔。如果您的 Timer 设置为 5000 毫秒间隔,并且您在大约 3000 毫秒时调用 Stop,则调用 Start 将导致 Timer 在引发 Tick 事件之前等待 5000 毫秒。

在 Windows 窗体应用程序中的任何 Timer 上调用 Stop 会导致来自应用程序中其他 Timer 组件的消息立即得到处理,因为所有 Timer 组件都在主应用程序线程上运行。如果您有两个 Timer 组件,一个设置为 700 毫秒,一个设置为 500 毫秒,并且您在第一个 Timer 上调用 Stop,您的应用程序可能会首先收到第二个组件的事件回调。

当您的应用程序临时冻结时,您还可以在调试器中点击暂停以检查到底发生了什么。

【讨论】:

您能解释一下为什么这可能会导致这种行为吗?据我了解,第 1 段不会导致它被调用两次,而只是增加下一个事件的等待时间。第二段听起来很相关,但要求有多个计时器,但 OP 没有提到这一点。

以上是关于为啥有时在计时器滴答事件中它会连续两次调用该方法?的主要内容,如果未能解决你的问题,请参考以下文章

C# Timer.Elapsed 事件连续触发两次

为啥在 UI 线程上输入锁会触发 OnPaint 事件?

如何避免计时器滴答中断主线程?

为啥我的计时器停止滴答作响?

STM32怎么用库函数使用滴答定时器?

使用计时器事件,表单不会顺利调整大小