0

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

特别参见 Hans Passant 的答案

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

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

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

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

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));
        }
    }
}
4

2 回答 2

1

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

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

于 2017-07-18T19:37:07.477 回答
-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;
    }
    

此外,我会将运行备份的时间保留在配置文件或注册表中,以便如果我想更改它,我可以在不重新编译我的服务的情况下进行操作。

于 2017-07-18T19:03:05.713 回答