System.Timers.Timer的问题。偶尔会多次射击

时间:2017-07-18 18:11:41

标签: c# .net system.timers.timer

我使用System.Timers.Timer每天备份一次SQL Server Express数据库。它似乎工作正常,大部分时间。有时,ElapsedEventHandler会以1或4分钟的间隔多次调用。它应该每天打一次。我将AutoReset设置为false,并在ElapsedEventHandler的末尾调用Start。此外,可能相关的是我重新计算间隔,以便计时器总是在接近凌晨1点时关闭。尽可能。数据库的备份可能需要几分钟,如果我没有改变间隔,时间可能会无法接受。我之所以提到这一点,是因为这些链接表明重置间隔可能存在问题:

特别参见Hans Passant的答案

但是,我不知道如何避免重置间隔。另外,我查看了System.Timers.Timer的代码。它似乎只是重置间隔将再次启动计时器。我不反对使用不同的计时器(System.Threading.Timer?),但我想先知道发生了什么。

我已粘贴下面的所有代码。我认为真正相关的部分是方法:DatabaseBackupTimerOnElapsed

最后,我提到程序有时会停止并重新启动(如果代码的其他部分中存在未捕获的异常)。我会假设即使没有调用Dispose,所有计时器都会在退出程序时被杀死?那是Timers不在操作系统中生活?

EDIT 我被要求放下一个小的,完整的,可验证的例子。我这样做。我保留了完整的例子,因为有人可能会声称(非常正确!)我发现了一个重要的细节。我已经运行了这段代码并且没有看到问题但是,它只是偶尔发生在原始代码中。

public class DatabaseCleanupManager 
{
    private const int MaxRetries = 5;
    private const int DatabaseBackupHourOneAm = 1;


    private Timer _databaseBackupTimer;

    public DatabaseCleanupManager()
    { }

    public void Initialize()
    {
        Console.WriteLine("Initialize");
        TimeSpan spanTimer = GetDBBackupTimeSpan(1);

        _databaseBackupTimer = new Timer(spanTimer.TotalMilliseconds)
        {
            AutoReset = false,
        };

        _databaseBackupTimer.Elapsed += DatabaseBackupTimerOnElapsed;
        _databaseBackupTimer.Start();
    }

    private TimeSpan GetDBBackupTimeSpan(int databaseBackupFrequencyInDays)
    {
        Console.WriteLine("GetDBBackupTimeSpan");
        DateTime dt1 = DateTime.Now;
        DateTime dt2 = new DateTime(dt1.Year, dt1.Month,
            dt1.Day, 1, 0, 0);
        // I'm really interested in a timer once a day.  I'm just trying to get it to happen quicker!
        //dt2 = dt2.AddDays(databaseBackupFrequencyInDays);
        dt2 = dt1.AddMinutes(4);

        TimeSpan spanTimer = dt2 - dt1;

        if (spanTimer.TotalMilliseconds < 0) // This could conceivably happen if the have 0 or a negative number (erroneously) for DatabaseBackupFrequencyInDays 
        {
            dt2 = new DateTime(dt1.Year, dt1.Month,
                dt1.Day, 1, 0, 0);
            //dt2 = dt2.AddDays(databaseBackupFrequencyInDays);
            dt2 = dt1.AddMinutes(4);
            spanTimer = dt2 - dt1;
        }
        return spanTimer;
    }
    public void PerformDatabaseMaintenance()
    {
        if (BackupCurrentDatabase())
        {
            var success = CleanupExpiredData();

            if (success)
            {
                Console.WriteLine("Database Maintenance Finished");
            }
        }

    }

    public void Dispose()
    {
        _databaseBackupTimer.Elapsed -= DatabaseBackupTimerOnElapsed;
        _databaseBackupTimer.Stop();
        _databaseBackupTimer.Dispose();
    }

    private void DatabaseBackupTimerOnElapsed(object sender, ElapsedEventArgs elapsedEventArgs)
    {
        try
        {
            Console.WriteLine("DatabaseBackupTimerOnElapsed at: " + DateTime.Now);
            PerformDatabaseMaintenance();

            TimeSpan spanTimer = GetDBBackupTimeSpan(1);

            // NOTICE I'm calculating Interval again.  Some posts suggested that this restarts timer
            _databaseBackupTimer.Interval = Math.Max(spanTimer.TotalMilliseconds, TimeSpan.FromMinutes(1).TotalMilliseconds);
            _databaseBackupTimer.Start();

        }
        catch (Exception )
        {
            // something went wrong - log problem and start timer again.
            _databaseBackupTimer.Start();
        }

    }

    private bool BackupCurrentDatabase()
    {
        // actually backup database but here I'll just sleep for 1 minute...
        Thread.Sleep(1000);
        Console.WriteLine("Backed up DB at: " + DateTime.Now);
        return true;
    }

    private bool CleanupExpiredData()
    {
        // Actually remove old SQL Server Express DB .bak files but here just sleep
        Thread.Sleep(1000);
        Console.WriteLine("Cleaned up old .Bak files at: " + DateTime.Now);
        return true;
    }


}

 class Program
{
    static void Main(string[] args)
    {
        DatabaseCleanupManager mgr = new DatabaseCleanupManager();
        mgr.Initialize();

        // here we'd normally be running other threads etc., but for here...
        Thread.Sleep(24*60*60*1000);  // sleep for 1 day
    }
}

结束编辑

public class DatabaseCleanupManager : IDatabaseCleanupManager
{
    private const int MaxRetries = 5;
    private const int DatabaseBackupHourOneAm = 1;

    private readonly ISystemConfiguration _systemConfiguration;
    private readonly IPopsicleRepository _repository;
    private readonly ISystemErrorFactory _systemErrorFactory;
    private readonly IAuthorizationManager _authorizationManager;
    private readonly IReportRobotState _robotStateReporter;
    private Timer _databaseBackupTimer;

    public DatabaseCleanupManager(
        IPopsicleRepository repository,
        ISystemConfiguration configuration,
        ISystemErrorFactory systemErrorFactory,
        IAuthorizationManager authorizationManager,
        IReportRobotState robotStateReporter)
    {
        if (repository == null)
            throw new ArgumentNullException("repository");

        if (configuration == null)
            throw new ArgumentNullException("configuration");

        if (systemErrorFactory == null)
            throw new ArgumentNullException("systemErrorFactory");

        if (authorizationManager == null)
            throw new ArgumentNullException("authorizationManager");

        if (robotStateReporter == null)
            throw new ArgumentNullException("robotStateReporter");

        _repository = repository;
        _systemConfiguration = configuration;
        _systemErrorFactory = systemErrorFactory;
        _authorizationManager = authorizationManager;
        _robotStateReporter = robotStateReporter;
    }

    public event EventHandler<SystemErrorEventArgs> SystemError;

    public event EventHandler<SystemErrorClearedEventArgs> SystemErrorCleared;


    public void Initialize()
    {

        TimeSpan spanTimer = GetDBBackupTimeSpan(_systemConfiguration.DatabaseBackupFrequencyInDays);

        _databaseBackupTimer = new Timer(spanTimer.TotalMilliseconds)
        {
            AutoReset = false,
        };

        _databaseBackupTimer.Elapsed += DatabaseBackupTimerOnElapsed;
        _databaseBackupTimer.Start();
    }

    private TimeSpan GetDBBackupTimeSpan(int databaseBackupFrequencyInDays)
    {
        DateTime dt1 = DateTime.Now;
        DateTime dt2 = new DateTime(dt1.Year, dt1.Month,
            dt1.Day, 1, 0, 0);
        dt2 = dt2.AddDays(_systemConfiguration.DatabaseBackupFrequencyInDays);

        TimeSpan spanTimer = dt2 - dt1;

        if (spanTimer.TotalMilliseconds < 0) // This could conceivably happen if the have 0 or a negative number (erroneously) for DatabaseBackupFrequencyInDays in configuration.json
        {
            dt2 = new DateTime(dt1.Year, dt1.Month,
                dt1.Day, 1, 0, 0);
            dt2 = dt2.AddDays(1);
            spanTimer = dt2 - dt1;
        }
        return spanTimer;
    }
    public void PerformDatabaseMaintenance()
    {
        if (BackupCurrentDatabase())
        {
            var success = CleanupExpiredData();

            if (success)
            {
                Logger.Log(LogLevel.Info, string.Format("Database Maintenance succeeded"));
                NotifySystemError(ErrorLevel.Log, ErrorCode.DatabaseBackupComplete, "Database backup completed");
            }
        }

    }

    public void Dispose()
    {
        _databaseBackupTimer.Elapsed -= DatabaseBackupTimerOnElapsed;
        _databaseBackupTimer.Stop();
        _databaseBackupTimer.Dispose();
    }

    private void DatabaseBackupTimerOnElapsed(object sender, ElapsedEventArgs elapsedEventArgs)
    {

        try
        {

            PerformDatabaseMaintenance();

            TimeSpan spanTimer = GetDBBackupTimeSpan(_systemConfiguration.DatabaseBackupFrequencyInDays);

            _databaseBackupTimer.Interval = Math.Max(spanTimer.TotalMilliseconds, TimeSpan.FromMinutes(10).TotalMilliseconds);
            _databaseBackupTimer.Start();

        }
        catch (Exception e)
        {
            Logger.Log(LogLevel.Warning,
                string.Format("Database Backup Failed: {0}, ",
                    e.Message));
            NotifySystemError(ErrorLevel.Log, ErrorCode.DatabaseBackupFailed,
                "Database backup failed ");

            _databaseBackupTimer.Start();
        }


    }



    private bool BackupCurrentDatabase()
    {
        try
        {
            _repository.Alerts.Count();
        }
        catch (Exception ex)
        {
            NotifySystemError(ErrorLevel.Log, ErrorCode.DatabaseBackupFailed, "Database backup failed - the database server does not respond or the database does not exist");
            throw new InvalidOperationException(string.Format("The DB does not exist : {0} Error {1}", _systemConfiguration.LocalDbPath, ex.Message));
        }

        if (!Directory.Exists(_systemConfiguration.LocalBackupFolderPath))
            Directory.CreateDirectory(_systemConfiguration.LocalBackupFolderPath);

        var tries = 0;
        var success = false;

        while (!success && tries < MaxRetries)
        {
            try
            {
                _repository.BackupDatabase(_systemConfiguration.LocalBackupFolderPath);
                success = true;
            }
            catch (Exception e)
            {
                Logger.Log(LogLevel.Warning, string.Format("Database Backup Failed: {0}, retrying backup", e.Message));
                Thread.Sleep(TimeSpan.FromSeconds(1));
                tries++;

                if (tries == MaxRetries)
                {
                    NotifySystemError(ErrorLevel.Log, ErrorCode.DatabaseBackupFailed, string.Format("Database backup failed - {0}", e.Message));
                }
            }
        }

        var backupDirectory = new DirectoryInfo(_systemConfiguration.LocalBackupFolderPath);
        var files = backupDirectory.GetFiles().OrderBy(f => f.CreationTime).ToArray();

        if (files.Length > _systemConfiguration.MaxDatabaseBackups)
        {
            for (var i = 0; i < (files.Length - _systemConfiguration.MaxDatabaseBackups); i++)
            {
                try
                {
                    files[i].Delete();
                }
                catch (Exception e)
                {
                    Logger.Log(LogLevel.Warning, string.Format("Failed to delete old backup: {0}", e.Message));
                }
            }
        }

        Logger.Log(LogLevel.Info, success ?
            "Database Backup succeeded" :
            string.Format("Database Backup failed after {0} retries", MaxRetries));

        return success;
    }

    private bool CleanupExpiredData()
    {
        var success = false;
        try
        {
            var expirationTime = DateTime.Now - TimeSpan.FromDays(_systemConfiguration.DatabaseDataExpirationInDays);
            _repository.DeleteTemperatureReadingsBeforeDate(expirationTime);
            _repository.DeleteTransactionsBeforeDate(expirationTime);
            success = true;
        }
        catch (Exception e)
        {
            Logger.Log(LogLevel.Warning, string.Format("Failed to cleanup expired data: {0}", e.Message));
            NotifySystemError(ErrorLevel.Log, ErrorCode.DatabaseBackupFailed, string.Format("Database cleanup of expired data failed - {0}", e.Message));
        }

        Logger.Log(LogLevel.Info, success ?
            string.Format("Database clean up expired data succeeded") :
            string.Format("Database clean up expired data failed"));

        return success;
    }

    private void NotifySystemError(ErrorLevel errorLevel, ErrorCode errorCode, string description)
    {
        var handler = SystemError;
        if (handler != null)
        {
            var systemError = _systemErrorFactory.CreateSystemError(errorLevel, errorCode, description);
            handler(this, new SystemErrorEventArgs(systemError));
        }
    }
}

2 个答案:

答案 0 :(得分:1)

我怀疑是多次调用Initialize,你可以做的是在创建新实例之前检查_databaseBackupTimer是否为空

如果它不为空,只需跳过该方法中的整个代码

答案 1 :(得分:-2)

我认为解决方案过于复杂。

  1. 除非您在事件期间明确停止计时器,否则计时器间隔将触发Elapsed事件,而与上次运行完成时的事件无关。这不应该是必要的。输入方法时,您可以简单地跟踪您是否正在运行。
  2. 将间隔设置为59999.这是一分钟的毫秒。然后在事件处理程序的入口处检查当前小时和分钟是否与您要备份的时间相对应。

    private bool running = false;
    private Timer timer = new Timer();
    //other code.
    private void Initialize()
    {
        timer.Interval = 59999;
        myTimer.Elapsed += TimerElapsed;
        timer.Start();
    }
    
    public void TimerElapsed(object sender, ElapsedEventArgs e)
    {
       if (running) return;
       DateTime dt = DateTime.Now();
    
       if (!(dt.Hour.Equals(1) && dt.Minute.Equals(0))) return;
    
       running = true;
       //other code
       running = false;
    }
    
  3. 此外,我会在配置文件或注册表中保留运行备份的时间,这样如果我想更改它,我可以在不重新编译服务的情况下执行此操作。