Problems with System.Timers.Timer. Firing more than once occasionally - c#

I'm using System.Timers.Timer to backup my SQL Server Express Database once a day. It seems to work fine, most of the time. Occasionally, the ElapsedEventHandler gets called multiple times at 1 or 4 minute intervals. It should just be hit once a day. I have AutoReset as false and I call Start at the end of the ElapsedEventHandler. Also, possibly relevant is that I do recalculate the interval so that the timer always goes off as close to 1 am. as possible. The backing up of the database can take a few minutes, and if I didn't change the interval, the time might drift unacceptably. I mention this because these links suggest there might be a problem with resetting the interval:
Thread-safety of System.Timers.Timer vs System.Threading.Timer
Multiple timer elapsed issue
See in particular the answers by Hans Passant
However, I don't see how I can avoid resetting the interval. Also, I looked into the code for System.Timers.Timer. It didn't seem like just resetting the interval would Start the timer again. I'm not opposed to using a different timer (System.Threading.Timer?) but I'd like to know what is going on first.
I've pasted all the code below. I would think the truly relevant part is the method: DatabaseBackupTimerOnElapsed
Finally, I'll mention that the program is sometimes stopped and restarted (if there are uncaught exceptions in other parts of the code). I would assume though that all timers are killed at the point of exiting the program even if Dispose is not called? That is Timers don't live on in the operating system?
EDIT
I was requested to put down a small, complete, verifiable example. I do so here. I've kept the full example as someone might claim (quite correctly!) that I took out an important detail. I've run this code and have NOT seen the problem but then, it only happens very occasionally with the original code.
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
}
}
END EDIT
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));
}
}
}

what I suspect is that Initialize is called multiple time, what you could do is check that the _databaseBackupTimer is null before creating a new instance
if it's not null, just skip the whole code in that method

I think the solution is overly complicated.
The timer interval will fire the Elapsed event independent of when the fact that a previous run had finished or not, unless you explicitly stop the timer during the event. This should not be necessary. You could simply keep track of whether you are running or not when you enter the method.
Set the interval to 59999. This is a millisecond short of a minute. Then at entry in the event handler check if the current hour and minute correspond to the time when you want to backup.
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;
}
In addition, I would keep the time of running the backup in a configuration file or registry so that if I want to change it, I could do that without re-compiling my service.

Related

CSHARP running while every X seconds but until bool is true

I would like to check updates from txt file on web server. and send a messagebox when there is version mismatch, I'm using this code and it works but I want it only to connect the server every 10 minutes and check the text file.
is there any option to do that?
maybe using another way to create this loop?
I also would like to send the messagebox only once
bool hasDisplayed = false;
private string UpdateCheckServer()
{
WebClient client = new WebClient();
Stream stream = client.OpenRead("http://localhost/update/update_version.txt");
StreamReader reader = new StreamReader(stream);
string serverversion = reader.ReadToEnd();
return serverversion;
}
private string UpdateCheckClient()
{
string ClientVersion = System.IO.File.ReadAllText("update_version.txt");
return ClientVersion;
}
private void UpdateCheckTimer()
{
while (!hasDisplayed)
{
if (starter.Enabled == true && UpdateCheckServer() == UpdateCheckClient())
{
//Nothing here
}
if (starter.Enabled == true && UpdateCheckServer() != UpdateCheckClient())
{
MessageBox.Show("not updated");
hasDisplayed = true;
}
}
An implementation based on timer can look like this:
private bool _equalVersion = true;
private System.Timers.Timer _timer = new System.Timers.Timer(1000 * 60 * 10); //millisecond * seconds * minutes
private void StartUpdateCheckTimer()
{
_timer.Elapsed += UpdateCheck;
_timer.Start();
}
private void UpdateCheck(object sender, ElapsedEventArgs e)
{
_timer.Stop();
_equalVersion = (UpdateCheckServer() == UpdateCheckClient());
if (!_equalVersion)
{
MessageBox.Show("not updated");
}
else
{
_timer.Start();
}
}
in this way you get a timer:
triggered each 10 minutes,
performing the check
notifying the version change (and stopping) or proceeding with the subsequent iterations
then you can add some more logic like for example reset of the check, more proper notification action (separating your check routine from the GUI part) and many more.
Another approach (as pointed out by #imsmn) can be to make your implementation System.Threading.Sleep based, but I'd suggest the first option based on timer.
After several tries and learning I managed to do that.
private string UpdateCheckServer()
{
WebClient client = new WebClient();
Stream stream = client.OpenRead("http://localhost/update/update_version.txt");
StreamReader reader = new StreamReader(stream);
string serverversion = reader.ReadToEnd();
return serverversion;
}
private string UpdateCheckClient()
{
string ClientVersion = System.IO.File.ReadAllText("update_version.txt");
return ClientVersion;
}
private void UpdateCheckTimer()
{
System.Windows.Forms.Timer t = new System.Windows.Forms.Timer();
t.Interval = 15000; // specify interval time as you want
t.Tick += new EventHandler(timer_Tick);
t.Start();
void timer_Tick(object sender, EventArgs e)
{
//Call method
if (starter.Enabled == true && UpdateCheckServer() != UpdateCheckClient())
{
t.Stop();
MessageBox.Show("not updated");
}
}
}

MySQL database back up taking from one database on server A to another database on server b

I am trying to export all databases on server A and place into other databases ( not yet created) onto server B.
I am attempting to use MySqlBackup .NET as I am trying to create my own windows service in C# to run at a scheduled time as there is no time for someone everyday to click a button to back up the databases and export into another database.
I am kind of stumped as of now i have this:
public partial class BackUpService : ServiceBase
{
System.Timers.Timer _timer = null;
bool backUpRunning = false;
public BackUpService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
BackUpLog.WriteEntry("Database Backup has begun.");
_timer = new System.Timers.Timer { Interval = 3600000 };
_timer.Elapsed += new System.Timers.ElapsedEventHandler(_timer_Elapsed);
_timer.Start();
}
protected override void OnStop()
{
_timer.Stop();
BackUpLog.WriteEntry("Database Backup has finished.");
}
public void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
TimeSpan start = new TimeSpan(3, 0, 0);
TimeSpan end = new TimeSpan(5, 0, 0);
TimeSpan now = DateTime.Now.TimeOfDay;
BackUpLog.WriteEntry("Checking if it's time to run the archive service and checking if the service is already running");
if (backUpRunning)
BackUpLog.WriteEntry("Service is already running");
if ((now > start) && (now < end) && !backUpRunning)
{
BackUpLog.WriteEntry("Service is not already running and the time of day is valid to run the service.");
try
{
backUpRunning = true;
BackUpLog.WriteEntry("Initialising archive service");
}
catch (Exception ex)
{
logArchiveServiceError("The archive service failed. It will return to step 1 and try again: " + ex.Message);
}
finally
{
backUpRunning = false;
}
}
}
private void logArchiveServiceError(string errorMessage)
{
BackUpLog.WriteEntry(errorMessage, EventLogEntryType.Error);
Emailer.SendErrorAlertEmail("Archive Service Error", errorMessage, null, null);
}
private void logArchiveServiceInfo(string info)
{
BackUpLog.WriteEntry(info, EventLogEntryType.Information);
}
//EXPORT DATABASES
private void Backup()
{
string sqlDumpFile;
string con ;
MySqlBackup backup = new MySqlBackup(con);
backup.Export(sqlDumpFile);
}
//IMPORT DATABASES
private void Restore()
{
string sqlDumpFile;
string con;
MySqlBackup backup = new MySqlBackup(con);
backup.Import(sqlDumpFile);
}
}
I then need to process any errors or notifications so that they send automatic emails to a work email.

Architecture of real-time, iteration application

Sorry for abstract question, but I'm looking for some samples/advices/articles on type of applications which does some equivalent operations in cycle, and every iteration of cycle should expose its result in certain portion of time (for instance, 10 seconds).
My application does synchronization of data between external WCF service and local database. In every iteration an application retrieves changes of data passing request to WCF service and puts changes to database and vice versa. One of most hard requirement for this application is that iterations should fire every ten seconds.
So here is the issues arises. How can I guarantee that iteration will finish for no more than 10 seconds?
I guess this type of applications called real-time applications (in maner of real-time OS).
DAL components that we use acts randomly on connection timeout behavior. So DB operations may take longer time than 10 seconds.
Here is the estimated code of one iteration:
Stopwatch s1 = new Stopwatch();
s1.Start();
Parallel.ForEach(Global.config.databases, new ParallelOptions { MaxDegreeOfParallelism = -1 }, (l) =>
{
Console.WriteLine("Started for {0}", l.key.name);
DB db = new DB(l.connectionString);
DateTime lastIterationTS = GetPreviousIterationTS(l.id);
ExternalService serv = new ExternalService(l.id);
List<ChangedData> ChangedDataDb = db.GetChangedData(DateTime.Now.AddSeconds((lastIterationTS == DateTime.MinValue) ? -300 : -1 * (DateTime.Now - lastIterationTS).Seconds));
List<Data> ChangedDataService = serv.GetModifiedData();
Action syncDBChanges = new Action(() =>
{
// Изменения в БД
foreach (ChangedData d in ChangedDataDb)
{
try
{
// ...
// analyzing & syncing
}
catch (Exception e)
{
logger.InfoEx("Exception_SyncDatabase", e.ToString());
}
}
}
);
Action syncService = new Action(() =>
{
foreach (Data d in ChangedDataService)
{
try
{
// ...
// analyzing & syncing
}
catch (Exception e)
{
logger.InfoEx("Exception_SyncService", e.ToString());
}
}
});
List<WaitHandle> handles = new List<WaitHandle>();
IAsyncResult ar1 = syncDBChanges.BeginInvoke(syncDBChanges.EndInvoke, null);
IAsyncResult ar2 = syncService.BeginInvoke(syncService.EndInvoke, null);
handles.Add(ar1.AsyncWaitHandle);
handles.Add(ar2.AsyncWaitHandle);
WaitHandle.WaitAll(handles.ToArray(), (int)((Global.config.syncModifiedInterval - 1) * 1000));
SetCurrentIterationTS(l.id);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
logger.InfoEx("Exception_Iteration", e.ToString());
continue;
}
}
logger.InfoEx("end_Iteration", IterationContextParams);
}
);
s1.Stop();
Console.WriteLine("Main iteration done for {0}...", s1.Elapsed);
You can consider a couple of options...
Kill the iteration if it exceeds more than 10 seconds and hope that the next iteration can complete process. The issue with this approach is that there is a good possibility that the none of the iterations will complete and therefore the synchronization process will never occur. I would recommend the following option...
If the iteration takes more than 10 seconds, wait for it to complete and skip the next iteration(s). This way you ensure the process completes atleast once. The following is a simplified code sample for reference...
class Updater
{
Timer timer = new Timer();
public object StateLock = new object();
public string State;
public Updater()
{
timer.Elapsed += timer_Elapsed;
timer.Interval = 10000;
timer.AutoReset = true;
timer.Start();
}
void timer_Elapsed(object sender, ElapsedEventArgs e)
{
if (State != "Running")
{
Process();
}
}
private void Process()
{
try
{
lock (StateLock)
{
State = "Running";
}
// Process
lock (StateLock)
{
State = "";
}
}
catch
{
throw;
}
}
}
...
class Program
{
static void Main(string[] args)
{
Updater updater = new Updater();
Console.ReadLine();
}
}
Quartz.net is an excellent scheduler for the .NET platform, which I think could suit your needs.
If you want to kill a job, you can implement IInterruptableJob. You should be able to add some cleanup code in the Interupt method to dispose of any db connections.
If you want finish a job, but only start another job if the last one is completed (which I think is the better option), you can implement IStatefulJob interface
I usually separate the update cycle from the actual timer
The timer does two things:
1) if the update is not running starts it.
2) if the service is already running set a flag for it to continue running.
The update cycle:
1)set running flag
2) do the update
3) set running flag to false
4) if continue running is set go to 1).
You might want to read up on the variety of Timer objects available in .Net: http://msdn.microsoft.com/en-us/magazine/cc164015.aspx
I personally like System.Threading.Timer because you can easily use lambdas, and it allows a state object to be passed if you create a separate callback.
I would also recommend using the System.Threading.Tasks library, because it allows you to gracefully handle cancellations in the case that the timer elapses before your work is completed. Msdn example: http://msdn.microsoft.com/en-us/library/dd537607.aspx
Here's an example of using these components together in a 10 minute timer:
Note: to do this with your sql database you'll need to set Asynchronous Processing=true; and MultipleActiveResultSets=True;
CancellationTokenSource cancelSource = new CancellationTokenSource();
System.Threading.Timer timer = new System.Threading.Timer(callback =>
{
//start sync
Task syncTask = Task.Factory.StartNew(syncAction =>
{
using (SqlConnection conn =
new SqlConnection(
ConfigurationManager.ConnectionStrings["db"].ConnectionString))
{
conn.Open();
using (SqlCommand syncCommand = new SqlCommand
{
CommandText = "SELECT getdate() \n WAITFOR DELAY '00:11'; ",
CommandTimeout = 600,
Transaction = conn.BeginTransaction(),
Connection = conn
})
{
try
{
IAsyncResult t = syncCommand.BeginExecuteNonQuery();
SpinWait.SpinUntil(() =>
(t.IsCompleted || cancelSource.Token.IsCancellationRequested));
if (cancelSource.Token.IsCancellationRequested && !t.IsCompleted)
syncCommand.Transaction.Rollback();
}
catch (TimeoutException timeoutException)
{
syncCommand.Transaction.Rollback();
//log a failed sync attepmt here
Console.WriteLine(timeoutException.ToString());
}
finally
{
syncCommand.Connection.Close();
}
}
}
}, null, cancelSource.Token);
//set up a timer for processing in the interim, save some time for rollback
System.Threading.Timer spinTimer = new System.Threading.Timer(c => {
cancelSource.Cancel();
}, null, TimeSpan.FromMinutes(9), TimeSpan.FromSeconds(5));
//spin here until the spintimer elapses;
//this is optional, but would be useful for debugging.
SpinWait.SpinUntil(()=>(syncTask.IsCompleted || cancelSource.Token.IsCancellationRequested));
if (syncTask.IsCompleted || cancelSource.Token.IsCancellationRequested)
spinTimer.Dispose();
}, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(10));
Perhaps try this. Please make sure you do not create and use any new threads in DoWork() method.
class DatabaseUpdater
{
private readonly Timer _timer;
private List<Thread> _threads;
private readonly List<DatabaseConfig> _dbConfigs;
public DatabaseUpdater(int seconds, List<DatabaseConfig> dbConfigs)
{
_timer = new Timer(seconds * 1000);
_timer.Elapsed += TimerElapsed;
_dbConfigs = dbConfigs;
}
public void Start()
{
StartThreads();
_timer.Start();
}
public void Stop()
{
_timer.Stop();
StopThreads();
}
void TimerElapsed(object sender, ElapsedEventArgs e)
{
StopThreads();
StartThreads();
}
private void StartThreads()
{
var newThreads = new List<Thread>();
foreach (var config in _dbConfigs)
{
var thread = new Thread(DoWork);
thread.Start(config);
newThreads.Add(thread);
}
_threads = newThreads;
}
private void StopThreads()
{
if (_threads == null) return;
var oldThreads = _threads;
foreach (var thread in oldThreads)
{
thread.Abort();
}
}
static void DoWork(object objConfig)
{
var dbConfig = objConfig as DatabaseConfig;
if (null == dbConfig) return;
var n = GetRandomNumber();
try
{
ConsoleWriteLine("Sync started for : {0} - {1} sec work.", dbConfig.Id, n);
// update/sync db
Thread.Sleep(1000 * n);
ConsoleWriteLine("Sync finished for : {0} - {1} sec work.", dbConfig.Id, n);
}
catch (Exception ex)
{
// cancel/rollback db transaction
ConsoleWriteLine("Sync cancelled for : {0} - {1} sec work.",
dbConfig.Id, n);
}
}
static readonly Random Random = new Random();
[MethodImpl(MethodImplOptions.Synchronized)]
static int GetRandomNumber()
{
return Random.Next(5, 20);
}
[MethodImpl(MethodImplOptions.Synchronized)]
static void ConsoleWriteLine(string format, params object[] arg)
{
Console.WriteLine(format, arg);
}
}
static void Main(string[] args)
{
var configs = new List<DatabaseConfig>();
for (var i = 1; i <= 3; i++)
{
configs.Add(new DatabaseConfig() { Id = i });
}
var databaseUpdater = new DatabaseUpdater(10, configs);
databaseUpdater.Start();
Console.ReadKey();
databaseUpdater.Stop();
}

Why does C# thread die?

This is my 1st C# project so I may be doing something obviously improper in the code below.
I am using .NET, WinForms (I think), and this is a desktop application until I get the bugs out.
UpdateGui() uses Invoke((MethodInvoker)delegate to update various GUI controls based on received serial data and
sends a GetStatus() command out the serial port 4 times a second.
Thread Read() reads the response from serial port whenever it arrives which should be near immediate.
SerialPortFixer is a SerialPort IOException Workaround in C# I found at
http://zachsaw.blogspot.com/2010/07/serialport-ioexception-workaround-in-c.html.
After one or both threads die I'll see something like
The thread 0x1288 has exited with code 0 (0x0).
in the debug code output.
Why do UpdateGui() and/or Read() eventually die?
public partial class UpdateStatus : Form
{
private readonly byte[] Command = new byte[32];
private readonly byte[] Status = new byte[32];
readonly Thread readThread;
private static readonly Mutex commandMutex = new Mutex();
private static readonly Mutex statusMutex = new Mutex();
...
public UpdateStatus()
{
InitializeComponent();
SerialPortFixer.Execute("COM2");
if (serialPort1.IsOpen)
{
serialPort1.Close();
}
try
{
serialPort1.Open();
}
catch (Exception e)
{
labelWarning.Text = LOST_COMMUNICATIONS + e;
labelStatus.Text = LOST_COMMUNICATIONS + e;
labelWarning.Visible = true;
}
readThread = new Thread(Read);
readThread.Start();
new Timer(UpdateGui, null, 0, 250);
}
static void ProcessStatus(byte[] status)
{
Status.State = (State) status[4];
Status.Speed = status[6]; // MSB
Status.Speed *= 256;
Status.Speed += status[5];
var Speed = Status.Speed/GEAR_RATIO;
Status.Speed = (int) Speed;
...
}
public void Read()
{
while (serialPort1 != null)
{
try
{
serialPort1.Read(Status, 0, 1);
if (Status[0] != StartCharacter[0]) continue;
serialPort1.Read(Status, 1, 1);
if (Status[1] != StartCharacter[1]) continue;
serialPort1.Read(Status, 2, 1);
if (Status[2] != (int)Command.GetStatus) continue;
serialPort1.Read(Status, 3, 1);
...
statusMutex.WaitOne();
ProcessStatus(Status);
Status.update = true;
statusMutex.ReleaseMutex();
}
catch (Exception e)
{
Console.WriteLine(#"ERROR! Read() " + e);
}
}
}
public void GetStatus()
{
const int parameterLength = 0; // For GetStatus
statusMutex.WaitOne();
Status.update = false;
statusMutex.ReleaseMutex();
commandMutex.WaitOne();
if (!SendCommand(Command.GetStatus, parameterLength))
{
Console.WriteLine(#"ERROR! SendCommand(GetStatus)");
}
commandMutex.ReleaseMutex();
}
private void UpdateGui(object x)
{
try
{
Invoke((MethodInvoker)delegate
{
Text = DateTime.Now.ToLongTimeString();
statusMutex.WaitOne();
if (Status.update)
{
if (Status.Speed > progressBarSpeed.Maximum)
{
Status.Speed = progressBarSpeed.Maximum;
}
progressBarSpeed.Value = Status.Speed;
labelSpeed.Text = Status.Speed + RPM;
...
}
else
{
labelWarning.Text = LOST_COMMUNICATIONS;
labelStatus.Text = LOST_COMMUNICATIONS;
labelWarning.Visible = true;
}
statusMutex.ReleaseMutex();
GetStatus();
});
}
catch (Exception e)
{
Console.WriteLine(#"ERROR! UpdateGui() " + e);
}
}
}
A thread will terminate when there's no more code to execute, or more specifically when the method you specify when you create thread returns.
Maybe serialport1 becomes null?
As for the update timer, there is a special purpose windows forms timer that runs periodically that doesn't require you to use Invoke. It's the right tool for the job

C# based Windows Service - Tries to do JIT Debugging in production

I am getting this error in my event logs for a service I put into production:
An unhandled win32 exception occurred
in RivWorks.FeedHandler.exe [5496].
Just-In-Time debugging this exception
failed with the following error:
Debugger could not be started because
no user is logged on.
I have it installed and running under a Win NT global account. I have no idea why it is trying to drop into debugging mode. It was built under the Release model. Running on the 4.0 Framework.
When I run on my dev machine, via an EXE entry point instead of the WinSvc entry point, everything runs just fine - BUT - I am already in "debug" mode.
Any ideas of what to look for?
2010-10-21 - NOTE - Changed the code base.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.IO;
using sysIO = System.IO;
using RivWorks.FeedHandler;
using System.Collections;
namespace RivWorks.FeedHandler.Service
{
public partial class FeedListener : ServiceBase
{
#region Declarations
private List<string> _keys = new List<string>();
private System.Threading.Timer _clock = null;
private FileSystemWatcher _watcher;
private BackgroundWorker _worker;
private Queue<string> _queue = new Queue<string>();
private bool _isDequeueing = false;
#endregion
#region Constructor
public FeedListener()
{
InitializeComponent();
}
#endregion
#region Start/Stop
protected override void OnStart(string[] args)
{
try
{
WriteToEventLog("Enter Start", EventLogEntryType.Information);
_keys.AddRange(new string[] { "csv", "xml", "zip", "rivx" });
_worker = new BackgroundWorker();
_worker.WorkerReportsProgress = true;
_worker.WorkerSupportsCancellation = true;
_worker.DoWork += new DoWorkEventHandler(BackgroundWorkerDoWork);
_worker.ProgressChanged += new ProgressChangedEventHandler(BackgroundWorkerProgressChanged);
_worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BackgroundWorkerRunWorkerCompleted);
_watcher = new FileSystemWatcher(AppSettings.Default.FTPRootPath, "*.*");
_watcher.IncludeSubdirectories = true;
_watcher.NotifyFilter = sysIO.NotifyFilters.DirectoryName | sysIO.NotifyFilters.FileName | sysIO.NotifyFilters.LastAccess | sysIO.NotifyFilters.CreationTime | sysIO.NotifyFilters.LastWrite;
_watcher.Created += new sysIO.FileSystemEventHandler(fileCreatedOrChanged);
_watcher.Changed += new sysIO.FileSystemEventHandler(fileCreatedOrChanged);
_watcher.EnableRaisingEvents = true;
// check every 15 minutes...
_clock = new System.Threading.Timer(Tick, null, 0, 900000);
WriteToEventLog("Exit Start", EventLogEntryType.Information);
}
catch (Exception ex)
{
WriteToEventLog(ex, EventLogEntryType.Error);
this.Stop();
}
}
protected override void OnStop()
{
try
{
_watcher.Dispose();
_watcher = null;
_clock.Dispose();
_clock = null;
_worker.Dispose();
_worker = null;
}
catch (Exception ex)
{
WriteToEventLog(ex, EventLogEntryType.Error);
}
}
#endregion
#region Event Handlers
void fileCreatedOrChanged(object sender, sysIO.FileSystemEventArgs e)
{
try
{
WriteToEventLog("Enter fileCreatedOrChanged", EventLogEntryType.Information);
if (!_queue.Contains(e.FullPath))
_queue.Enqueue(e.FullPath);
if (!_isDequeueing)
DeQueue();
}
catch (Exception ex)
{
WriteToEventLog(ex, EventLogEntryType.Error);
}
}
#endregion
#region Do work on another Thread
void BackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
{
try
{
WriteToEventLog("Enter BackgroundWorkerDoWork", EventLogEntryType.Information);
BackgroundWorker bw = sender as BackgroundWorker;
WriteToEventLog("Create Handler", EventLogEntryType.Information);
RivWorks.FeedHandler.Library.Handler handler = new RivWorks.FeedHandler.Library.Handler(Convert.ToBoolean(AppSettings.Default.InProduction), AppSettings.Default.ArchivePath);
WriteToEventLog("Setup Handler", EventLogEntryType.Information);
handler.Keys = _keys;
handler.RootDirectory = AppSettings.Default.RootDirectory;
handler.FtpPath = AppSettings.Default.FTPRootPath;
handler.WorkPath = AppSettings.Default.WorkPath;
handler.ArchivePath = AppSettings.Default.ArchivePath;
handler.EmailHost = AppSettings.Default.EmailHost;
handler.EmailPassword = AppSettings.Default.EmailPassword;
handler.EmailUser = AppSettings.Default.EmailUser;
handler.ErrorNotificationRecipients = AppSettings.Default.ErrorNotificationRecipients;
handler.InProduction = Convert.ToBoolean(AppSettings.Default.InProduction);
Library.DTO.FileHandler fileHandler = new Library.DTO.FileHandler(handler.FtpPath, handler.WorkPath, handler.ArchivePath, (string)e.Argument);
WriteToEventLog("Call Handler.Execute", EventLogEntryType.Information);
handler.Execute(bw, e, fileHandler);
}
catch (Exception ex)
{
WriteToEventLog(ex, EventLogEntryType.Error);
}
finally
{
WriteToEventLog("Exit BackgroundWorkerDoWork", EventLogEntryType.Information);
}
}
void BackgroundWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
{
try
{
if (e.ProgressPercentage >= 100)
{
}
}
catch (Exception ex)
{
WriteToEventLog(ex, EventLogEntryType.Error);
}
}
void BackgroundWorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
try
{
if (e.Cancelled)
{
WriteToEventLog("Cancelled.", EventLogEntryType.Warning);
if (e.Error != null)
{
WriteToEventLog(e.Error, EventLogEntryType.Error);
}
}
else if (e.Error != null)
{
WriteToEventLog(e.Error, EventLogEntryType.Error);
}
else
{
WriteToEventLog("Successfully completed.", EventLogEntryType.Information);
}
}
catch (Exception ex)
{
WriteToEventLog(ex, EventLogEntryType.Error);
}
}
#endregion
#region Private Methods
private void Tick(object data)
{
try
{
if (!_isDequeueing)
{
WriteToEventLog("Enter Tick. FTP Root = " + AppSettings.Default.FTPRootPath, EventLogEntryType.Information);
foreach (string key in _keys)
{
List<string> files = Directory.GetFiles(Path.Combine(AppSettings.Default.FTPRootPath), "*." + key, SearchOption.AllDirectories).ToList();
foreach (string fileName in files)
{
if (File.Exists(fileName))
{
// Toss this file name into the Queue...
WriteToEventLog("Call _queue.Enqueue(" + fileName + ")", EventLogEntryType.Information);
if (!_queue.Contains(fileName))
_queue.Enqueue(fileName);
}
}
}
// Now, start handling the list of files...
DeQueue();
}
}
catch (Exception ex)
{
WriteToEventLog(ex, EventLogEntryType.Error);
}
finally
{
WriteToEventLog("Exit Tick", EventLogEntryType.Information);
}
}
private void DeQueue()
{
try
{
_isDequeueing = true;
WriteToEventLog("Enter DeQueue", EventLogEntryType.Information);
while (_queue.Count > 0)
{
string queuedFile = _queue.Dequeue();
WriteToEventLog("DeQueued " + queuedFile, EventLogEntryType.Information);
bool isValid = false;
foreach (string key in _keys)
{
if (Path.GetExtension(queuedFile).Replace(".", "").Equals(key, StringComparison.CurrentCultureIgnoreCase))
isValid = true;
}
if (isValid)
{
// Now, spin up a new thread and do the work on the file, based on file type...
WriteToEventLog("Call RunWorkerAsync", EventLogEntryType.Information);
string UserName = Path.GetDirectoryName(queuedFile).Replace(AppSettings.Default.FTPRootPath, "").Replace("\\", "");
int i = 0;
DateTime sTime = DateTime.Now;
DateTime eTime = DateTime.Now;
_worker.RunWorkerAsync(queuedFile); // goes to BackgroundWorkerDoWork(object sender, DoWorkEventArgs e) //
while(_worker.IsBusy)
{
System.Threading.Thread.Sleep(5000);
i++;
}
eTime = DateTime.Now;
TimeSpan ts = new TimeSpan(eTime.Ticks - sTime.Ticks);
string msg = String.Format("Import for {0} started at {1} and ended at {2}. It took {3} cycles and the elapsed time was {4}:{5}:{6}.", UserName, sTime, eTime, i, ts.Hours, ts.Minutes, ts.Seconds);
WriteToEventLog(msg, EventLogEntryType.Information);
}
}
}
catch (Exception ex)
{
WriteToEventLog(ex, EventLogEntryType.Error);
}
finally
{
_isDequeueing = false;
WriteToEventLog("Exit DeQueue", EventLogEntryType.Information);
}
}
private void WriteToEventLog(Exception ex, EventLogEntryType eventLogEntryType)
{
try
{
string message = string.Empty;
string sTrace = ex.StackTrace;
while (ex != null)
{
message = message + Environment.NewLine + Environment.NewLine + ex.Message;
ex = ex.InnerException;
}
message = message + Environment.NewLine + Environment.NewLine + sTrace;
WriteToEventLog(message, eventLogEntryType);
}
catch (Exception ex2)
{
WriteToEventLog(ex2.Message, EventLogEntryType.Error);
}
}
private void WriteToEventLog(string message, EventLogEntryType eventLogEntryType)
{
try
{
this.EventLog.WriteEntry(message, eventLogEntryType);
}
catch (Exception ex)
{
throw ex;
}
}
#endregion
}
}
2010-10-20 - NOTE - Added the Service code file. Maybe there is an elementary mistake in here?
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.IO;
using sysIO = System.IO;
using RivWorks.FeedHandler;
namespace RivWorks.FeedHandler.Service
{
public partial class FeedListener : ServiceBase
{
#region Declarations
private List<string> _keys = new List<string>();
private System.Threading.Timer _clock = null;
private FileSystemWatcher _watcher;
private BackgroundWorker _worker;
static private bool _isBusy = false;
#endregion
#region Constructor
public FeedListener()
{
InitializeComponent();
}
#endregion
#region Start/Stop
protected override void OnStart(string[] args)
{
try
{
_keys.AddRange(new string[] { "csv", "xml", "zip", "rivx" });
_worker = new BackgroundWorker();
_worker.WorkerReportsProgress = true;
_worker.WorkerSupportsCancellation = true;
_worker.DoWork += new DoWorkEventHandler(BackgroundWorkerDoWork);
_worker.ProgressChanged += new ProgressChangedEventHandler(BackgroundWorkerProgressChanged);
_worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BackgroundWorkerRunWorkerCompleted);
_watcher = new FileSystemWatcher(AppSettings.Default.FTPRootPath, "*.*");
_watcher.IncludeSubdirectories = true;
_watcher.NotifyFilter = sysIO.NotifyFilters.DirectoryName | sysIO.NotifyFilters.FileName | sysIO.NotifyFilters.LastAccess | sysIO.NotifyFilters.CreationTime | sysIO.NotifyFilters.LastWrite;
_watcher.Created += new sysIO.FileSystemEventHandler(fileCreatedOrChanged);
_watcher.Changed += new sysIO.FileSystemEventHandler(fileCreatedOrChanged);
_watcher.EnableRaisingEvents = true;
// check every 5 minutes...
_clock = new System.Threading.Timer(Tick, null, 0, 300000);
}
catch (Exception ex)
{
WriteToEventLog(ex, EventLogEntryType.Error);
this.Stop();
}
}
protected override void OnStop()
{
try
{
_watcher.Dispose();
_watcher = null;
_clock.Dispose();
_clock = null;
_worker.Dispose();
_worker = null;
}
catch (Exception ex)
{
WriteToEventLog(ex, EventLogEntryType.Error);
}
}
#endregion
#region Event Handlers
void fileCreatedOrChanged(object sender, sysIO.FileSystemEventArgs e)
{
try
{
DTO.BackgroundWorkerEventArgs eventArgs = new DTO.BackgroundWorkerEventArgs();
sysIO.WatcherChangeTypes myType = e.ChangeType;
bool isValid = false;
foreach (string key in _keys)
{
if (Path.GetExtension(e.FullPath).Replace(".", "").Equals(key, StringComparison.CurrentCultureIgnoreCase))
isValid = true;
}
if (isValid)
{
eventArgs.PathAndFile = e.FullPath;
eventArgs.Key = Path.GetExtension(e.FullPath).ToLower().Replace(".", "");
eventArgs.FileName = Path.GetFileName(e.FullPath);
eventArgs.Path = Path.GetDirectoryName(e.FullPath);
eventArgs.UserName = Path.GetDirectoryName(e.FullPath).Replace(AppSettings.Default.FTPRootPath, "").Replace("\\", "");
eventArgs.IsRunning = true;
System.Threading.Thread.Sleep(30000);
// Now, spin up a new thread and do the work on the file, based on file type...
_isBusy = true;
_worker.RunWorkerAsync(eventArgs); // goes to BackgroundWorkerDoWork(object sender, DoWorkEventArgs e) //
int i = 0;
DateTime sTime = DateTime.Now;
DateTime eTime = DateTime.Now;
while (_isBusy)
{
System.Threading.Thread.Sleep(5000);
i++;
}
eTime = DateTime.Now;
TimeSpan ts = new TimeSpan(eTime.Ticks - sTime.Ticks);
string msg = String.Format("Import for {0} started at {1} and ended at {2}. It took {3} cycles and the elapsed time was {4}:{5}:{6}.", eventArgs.UserName, sTime, eTime, i, ts.Hours, ts.Minutes, ts.Seconds);
WriteToEventLog(msg, EventLogEntryType.Information);
}
}
catch (Exception ex)
{
WriteToEventLog(ex, EventLogEntryType.Error);
}
}
#endregion
#region Do work on another Thread
void BackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
{
try
{
RivWorks.FeedHandler.Handler handler = new RivWorks.FeedHandler.Handler();
BackgroundWorker bw = sender as BackgroundWorker;
handler.Execute(bw, e);
}
catch (Exception ex)
{
WriteToEventLog(ex, EventLogEntryType.Error);
}
finally
{
_isBusy = false;
}
}
void BackgroundWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
{
try
{
if (e.ProgressPercentage >= 100)
{
_isBusy = false;
}
}
catch (Exception ex)
{
WriteToEventLog(ex, EventLogEntryType.Error);
}
}
void BackgroundWorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
try
{
if (e.Cancelled)
{
WriteToEventLog("Cancelled.", EventLogEntryType.Warning);
if (e.Error != null)
{
WriteToEventLog(e.Error, EventLogEntryType.Error);
}
}
else if (e.Error != null)
{
WriteToEventLog(e.Error, EventLogEntryType.Error);
}
else
{
WriteToEventLog("Successfully completed.", EventLogEntryType.Information);
}
_isBusy = false;
}
catch (Exception ex)
{
WriteToEventLog(ex, EventLogEntryType.Error);
}
}
#endregion
#region Private Methods
private void Tick(object data)
{
try
{
foreach (string key in _keys)
{
List<string> files = Directory.GetFiles(Path.Combine(AppSettings.Default.FTPRootPath), "*." + key, SearchOption.AllDirectories).ToList();
foreach (string fileName in files)
{
System.Threading.Thread.Sleep(5000);
if (File.Exists(fileName))
{
DateTime lat = File.GetLastWriteTime(fileName);
try
{
File.SetLastWriteTime(fileName, DateTime.Now);
}
catch
{
// just catch and ignore with a short pause...
System.Threading.Thread.Sleep(5000);
}
}
}
}
}
catch (Exception ex)
{
WriteToEventLog(ex, EventLogEntryType.Error);
}
}
private void WriteToEventLog(Exception ex, EventLogEntryType eventLogEntryType)
{
try
{
string message = string.Empty;
string sTrace = ex.StackTrace;
while (ex != null)
{
message = message + Environment.NewLine + Environment.NewLine + ex.Message;
ex = ex.InnerException;
}
message = message + Environment.NewLine + Environment.NewLine + sTrace;
this.EventLog.WriteEntry(message, eventLogEntryType);
}
catch (Exception ex2)
{
WriteToEventLog(ex2, EventLogEntryType.Error);
}
}
private void WriteToEventLog(string message, EventLogEntryType eventLogEntryType)
{
try
{
this.EventLog.WriteEntry(message, eventLogEntryType);
}
catch (Exception ex)
{
WriteToEventLog(ex, EventLogEntryType.Error);
}
}
#endregion
}
}
Even though it's running as a release exe, you'll still be given the option to attach to a debugger when the app crashes... you just won't see debug symbols, just assembly :)
I believe it's the Dr. Watson process that catches app errors for debugging... Because your app is a service, Dr. Watson can't interact with the desktop, giving you the error you see. You can go to the service properties and mark "allow service to interact with the desktop", found on the LogOn tab, which should then give you a Dr. Watson popup when the app crashes.
Steps to disable Dr. Watson are here:
http://support.microsoft.com/kb/188296
If you want to debug the app on the server, you can enable remote debugging on the server, and attach Visual Studio to the process... if you want to try this, I can give you more tips for debugging a windows service remotely.
HTH,
James
* Edit *
Based on the code you provided, I'd look at the following areas:
Is AppSettings.Default.FTPRootPath set correctly in App.Config?
Are there changes happening to that directory immediately when the service starts? You have a timer commented as "check every five minutes", which is a little confusing, because the FileSystemWatcher will start receiving events as soon as you set EnableRaisingEvents to true. So the issue could actually lie within fileCreatedOrChanged
Along those lines, you have one BackgroundWorker servicing multiple events, and worse, you're firing the handler asynchronously. This is my most likely suspect, because if you call _worker.RunWorkerAsync() again while the first job is running, you'll get an InvalidOperationException. Though I'm not sure why you wouldn't see that in the log
You're using the timer to update the last write time for all files in the watched directory, and you do this every five seconds. This seems like a very bad idea... I'm not sure what you're trying to accomplish. This will fire your FileSystemWatcher's changed event, which would explain why you're crashing less than 10 seconds after you start (the timer's initial tick is set to fire immediately, meaning five seconds later you're changing all the file times, triggering the FileSystemWatcher multiple times shortly after that)
So my best guess is that within five seconds, you've begun firing multiple RunWorkAsync() calls on the same BackgroundWorker, which is a no-no : )
Setting the static variable _isBusy to true/false isn't reliable because you're multi-threading with the BackgroundWorkers... you need to use a Mutex or some other lock, but doesn't that really defeat the purpose of using a BackgroundWorker?
Also, if you wanted to use something like an isBusy flag, it would have to look more like:
while (_isBusy) {
System.Threading.Thread.Sleep(5000);
}
_isBusy = true;
_worker.RunWorkerAsync(eventArgs);
You need _isBusy to be false before you try to launch the Background worker... the way you have it, if the event fires 100 times, you'll make 100 calls.
The easiest solution to your problem would be to create a new BackgroundWorker in the fileCreatedOrChanged method every time the event fires... there's overhead involved in creating so many new threads, but if the work being done in this method is significant, it will be worth the overhead.
You might be able to rely on the built-in BackgroundWorker.IsBusy property, but again, I'd have to question the benefit of asynchronous threading if you're just going to block until the background worker completes.
** Edit **
I understand now what you're trying to accomplish with the initial file timestamp changes... I think you would do better to leave the timestamps alone, but just run through a startup loop to process existing files. You can spawn a background worker thread for each one, just like you do on the FileSystemWatcher nofications. The way you're handling it is deliberately creating a side-effect to trigger the result you want.
I'm losing track a little bit in the growing complexity... the whole queue/dequeue thing might be unnecessary. Or maybe I just am not seeing a need that is truly there. Again, what strikes me is that you are launching the background worker asynchronously, then putting the main thread to sleep until it finishes.
When you put the main thread to sleep, no events will get processed, so you are truly throttling the multi-threading to one thread. I see that you want to write to the event log how long it took for a thread to finish. I will start a second answer to address that if I get a chance to, but the gist of it is to pass a Stopwatch class (which will give you an accurate count of either milliseconds or CPU ticks that pass during an operation) to the DoWorkEventArgs.Result property.
But the code you requested! Basically, wherever you decide to call _worker.RunWorkerAsync(queuedFile), rather than run one class-level BackgroundWorker create a new one each time. Pass all the same parameters for the event handlers, etc. Your service entry point would drop all the BGW references and look like:
protected override void OnStart(string[] args)
{
try
{
_keys.AddRange(new string[] { "csv", "xml", "zip", "rivx" });
_watcher = new FileSystemWatcher(AppSettings.Default.FTPRootPath, "*.*");
_watcher.IncludeSubdirectories = true;
_watcher.NotifyFilter = sysIO.NotifyFilters.DirectoryName | sysIO.NotifyFilters.FileName | sysIO.NotifyFilters.LastAccess | sysIO.NotifyFilters.CreationTime | sysIO.NotifyFilters.LastWrite;
_watcher.Created += new sysIO.FileSystemEventHandler(fileCreatedOrChanged);
_watcher.Changed += new sysIO.FileSystemEventHandler(fileCreatedOrChanged);
_watcher.EnableRaisingEvents = true;
WriteToEventLog("Exit Start", EventLogEntryType.Information);
}
and the code where you run the BGW asynchronously would become:
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.DoWork += new DoWorkEventHandler(BackgroundWorkerDoWork);
worker.ProgressChanged += BackgroundWorkerProgressChanged; // Note you don't need
worker.RunWorkerCompleted += BackgroundWorkerRunWorkerCompleted; // the 'new' here
worker.RunWorkerAsync(queuedFile); // goes to BackgroundWorkerDoWork(object sender, DoWorkEventArgs e) //
The error message tells you that it couldn't attach a debugger to let you inspect the exception. This is completely unrelated to the fact that this is a release build. Release build and debug builds can both be debugged (fortunately!).
Debugging services is a little different from debugging regular applications. Please check this guide for some advice.

Categories

Resources