I read once the idea that Exceptions should only be caught if you can handle them, and this makes sense to me. Besides a Top level, uncaught exception handler for client facing applications, I usually avoid the try catch blocks unless I am going to do something specific at that point in the code. I wrote a database monitor that was used to continually run in a Windows Service and involved several factors that called for robust program recovery- database timeouts, file I/O, multi threaded operations. So there were specific failures that I knew about and could write around. Here are some of the routines:
}
else
{
myTimer.Dispose();
e.Result = filename;
}
try
{
DataTable myTable = new DataTable();
myTable.ExtendedProperties["filename"] = filename;
myAdp.Fill(myTable);
e.Result = myTable;
if (bw.CancellationPending)
{
e.Result = filename;
e.Cancel = true;
myTimer.Dispose();
}
}
catch (OracleException ex)
{
logThis(ex.ToString(), EventLogEntryType.Warning);
e.Result = filename;//move on
}
catch (Exception ex)
{
logThis(ex.ToString(), EventLogEntryType.Error);
throw;
}
finally
{
if (myAdp.SelectCommand.Connection.State == ConnectionState.Open)
{
myAdp.Dispose();
}
myTimer.Dispose();
}
In this case we are pinging a database batch process every few minutes, and occasionally we might get a timeout, but we don’t want to bring the whole Service down, because it also monitors other data sources, and we can try again in a few minutes and find the same database is now responsive. The end result of a timeout might be a blip on a graph or chart, or a yellow light on a status gauge, that turns green if the database comes back and turns red the longer it has been since the database was responsive.
Here we are storing our ping result info in local xml files. In retrospect, there may have been easier options, but this allows us to add monitored sources dynamically, and manage the XML files dynamically, rather than have to create them in a database ahead of time. I wrote in a level of tolerance for concurrent file access Reads and Writes just using sleep and loops and it works perfectly for this case. Once again, we don’t need a perfect level of detail since we are running every few minutes. An occasional lost entry does not get noticed.
private void saveXML(XElement myRoot, string filename)
{
int retrySave = 10;
while (retrySave > 0)
{
try
{
myRoot.Save(filename);
}
catch (IOException exIO)
{
Thread.Sleep(1000);
retrySave--;
continue;
}
break;
}
}
private XElement loadXML(string filename)
{
XElement myRoot = null;
using (FileStream myStream =
new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
int retryLoad = 10;
while (retryLoad > 0)
{
try
{
using (XmlReader xr = XmlReader.Create(myStream))
{
myRoot = XElement.Load(xr);
}
}
catch (XmlException x)
{
// we expect some rootElement is missing ... from other threads
retryLoad--;
Thread.Sleep(1000);
continue;
}
break;
}
}
return myRoot;
}
One of the other features of this monitor, was that we would run against an unknown number of data sources, and they weren’t related. Each source was separate, and so threading out the pings was simple, but some datasources acted differently than others. Oracle TNS Listener would simply not return if there was no database endpoint, and so we had to run another thread using the Timer to cancel our thread in case this happened. I think at the time I put this together, a parallel concurrency library had just come out, but wasn’t officially part of .Net, or it wasn’t out and so up to this point most of the threading projects I did and I researched were all done with BackgroundWorkers.
private void dbMonStart(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
String db = e.Argument.ToString();
// set a watch timer to cancel thread on sql timeout (used more for dev when taken offline not prod
System.Threading.Timer myTimer = new System.Threading.Timer(cancelMyThread, bw, 30000, System.Threading.Timeout.Infinite);
using (OracleDataAdapter myAdp = new OracleDataAdapter())
{
myAdp.SelectCommand = cmd;
myAdp.SelectCommand.Connection = new OracleConnection(conn);
try
{
DataTable myTable = new DataTable();
myTable.ExtendedProperties["dbname"] = db;
myAdp.Fill(myTable);
e.Result = myTable;
if (bw.CancellationPending)
{
e.Result = db;
e.Cancel = true;
myTimer.Dispose();
}
}
CancelMyThread is simple enough:
private void cancelMyThread(object myThread)
{
BackgroundWorker bw = myThread as BackgroundWorker;
if (bw.IsBusy) { bw.CancelAsync(); }
and that’s it.