我使用 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));
}
}
}