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

时间:2015-12-16 10:52:07

标签: c# .net winforms

我有两个项目的解决方案。 在库项目中,我添加了一个公共静态bool变量,并将其设置为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秒。

2 个答案:

答案 0 :(得分:2)

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

  

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

     

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

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

答案 1 :(得分:2)

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

您需要改进代码,不要尝试两次加载相同的文件。简单地通过使用存储最后使用的路径的变量来完成。并且保持超时更加温和,5秒是相当长的时间来挂起UI,最多一秒是合理的。另请注意,您根本不需要超时。由于计时器确保您在一秒钟后再次尝试。

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

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