finally is not always being executed...

  • Thread starter Thread starter Guest
  • Start date Start date
G

Guest

Hi,

I was under the impression that the finally block always got executed no
matter what. Well I ran into a situtation that I can duplicate from a
console app as well as a web application.

For the console app if the try block is executing/processing its clode and
the console app is closed the finally block is not called.

The same is true for the web app. If the page_load or button_click is
processing cose and it is wrapped by a try/catch/finally or try/finally the
finally is not called if the web page is closed.

Give it a try, I was blown away by this. I thought no mwatter what the
finally block was called and I have very important logic that needs to be
called.

Below is very sloppy code, but it will do the trick to show this isn't
working. The same goes for the console. Just put the try/finally in the
main.

Code:
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.IO;

public partial class GeneralTest : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
Response.Write(DateTime.Now.ToString());
}
protected void Button1_Click(object sender, EventArgs e)
{
try
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
for (int x = 0; x < 100000000; x++)
{
for (int y = 0; y < 100000000; y++)
{
sb.Append(x.ToString() + " " + y.ToString());
}
}
}
finally
{
FileStream fsUpdateBackupDateTime = new FileStream(@"c:\zzzzcjm.txt",
FileMode.OpenOrCreate);
StreamWriter sw = new StreamWriter(fsUpdateBackupDateTime);
sw.Write(DateTime.Now.ToString());
sw.Flush();
sw.Close();
}
}


}

I guess is there anything that can be done or a way to make the finally or a
block of code to be called if the console app or web application is closed?

Thanks for your time!!
cmullins
 
I forgot to add this was tested in vs2003 and vs2005. The block of code is
from my vs2005 test.

Thanks again,
cmullins
 
At first I was not surprised I was thinking he's just going to crash the
computer!
Silly me, of course .NET handles not enough memory gracefully!
I mean just throw OutOfMemoryException() ;-)
I've got this zzccmmxxbbll.txt file no problem!

just to show you I attached my console application and crash test.
BTW there is a faster way to create a not enough memory situation (which
stress less the system!).

just write
int[] array = new int[int.Maxvalue]; ;-)
 
have you think you might simply not have rights to write the file.
after all it's an ASP.NET application you are speaking about!
 
In a console app, what you describe makes perfect sense...stop the
application and there is nothing left to keep processing, but in a web app
(ASP.NET) all the server-side processing takes place before the client ever
sees the page. You can't wrap Page_Load (or any event handler for that
matter) inside of a Try...Catch, but you certainly can place a Try...Catch
inside of an event handler.

BUT.....there is a difference in what you've experience in a console app.
vs. a web app. In VS.NET, if you close the browser (presumably, you are
talking about closing it before the page processing is completed), you are
telling VS.NET that you do not wish to continue to debug the application and
so the code processing is terminated. But, in a real deployment, if a
browser were to be closed, it would not signal the server to stop processing
the application (i.e.. Application_End).
 
No there is nothing you can do in the case of a OOM exception, you consumed
all the memory available for the process, so there is nothing left for the
program to continue it's execution of the finally block.

Willy.
 
Since, what I'm writting cannot be a webapp, I believe I'll create a windows
app instead. I'll will hook into one of the closing events for my logic, use
two threads and a stop button or something close to one of those.

Thanks again for the replies!!
cmullins
 
Willy,

Actually, there is no exception being generated. So, the OOM exception
isn't coming into play. Basically, if I manually throw an exception then of
course the finally is called.

However, in my situation I'm actually copying files from one location to
another. At the end of the copy process I'm writing out the date time of
when the process started. This way only modified files will be copied the
next time. What I want to do is if the user closes the console then write
out the date time. Otherwise, the date time isn't being updated since the
finally isn't called.

Thanks,
cmullins
 
cmullins said:
Actually, there is no exception being generated. So, the OOM exception
isn't coming into play.

In that case, presumably the code you posted isn't actually the code in
question, which makes it much harder to work out what's actually going
on.

Are you sure that the finally block itself isn't generating an
exception, by the way? Have you tried putting something which is bound
to work (and let you know that it's executed) as the first bit of the
finally block?

Could you post a short but complete program which demonstrates the
problem? A console application would be much easier to work with than
an ASP.NET app.

See http://www.pobox.com/~skeet/csharp/complete.html for details of
what I mean by that.
 
Jon,

Sure thing. Here is the code I was using. If the code completes the
finally block is called correctly. It is only when the console is closed
while files are being copied.

using System;
using System.Collections;
using System.Text;
using System.IO;

namespace BackupComputerConsole
{
class StartBackup
{
FileStream fs;
System.IO.StreamWriter sw;
DateTime lastBackupDateTimeValue;
DateTime thisBackupDateTime;
const string BackupLastDateTimeFileName = @"c:\backupLastDateTime.txt";
const string BackupLogFileName = @"c:\backuplog.txt";


static void Main(string[] args)
{
// Ensure the parameters are passed
if(args.Length!=3)
{
Console.WriteLine("");
Console.WriteLine("");
Console.WriteLine("Usage: ");
Console.WriteLine("");
Console.WriteLine("StartBackup.exe <string: Directory to copy> <string:
Destination Directory> <bool: Overwrite files>");
Console.WriteLine("");
Console.WriteLine("");
Console.WriteLine("Example: ");
Console.WriteLine("");
Console.WriteLine(@"StartBackup.exe c:\ x:\ true");
Console.WriteLine("");
Console.WriteLine("");
Console.WriteLine("Press any key to exit...");
Console.ReadLine();
}
else
{
new StartBackup().OnStart(args[0], args[1], Boolean.Parse(args[2]));
}
}


#region Private Properties


private DateTime LastBackupDateTime
{
get
{
if (this.lastBackupDateTimeValue == DateTime.MinValue)
{
try
{
FileStream fsGetLastBackupDateTime = new
FileStream(@"c:\backupLastDateTime.txt", FileMode.Open);
StreamReader sr = new StreamReader(fsGetLastBackupDateTime);
this.lastBackupDateTimeValue = DateTime.Parse(sr.ReadLine());
sr.Close();
}
catch
{
this.lastBackupDateTimeValue = DateTime.MinValue;
}
}
return this.lastBackupDateTimeValue;
}

}

#endregion

#region Private Methods


private string CheckAndCreateDirectory(string directoryName)
{
if (directoryName.Substring(3).Length > 0)
{
// Append the directory with a slash
if (!directoryName.EndsWith(@"\"))
{
directoryName += @"\";
}

// Create the directory if it doesn't exist
if (!Directory.Exists(directoryName))
{
Directory.CreateDirectory(directoryName);
}
}
return directoryName;
}


private void UpdateLastBackup()
{
FileStream fsUpdateBackupDateTime = new
FileStream(BackupLastDateTimeFileName, FileMode.OpenOrCreate);
StreamWriter sw = new StreamWriter(fsUpdateBackupDateTime);
sw.Write(this.thisBackupDateTime);
sw.Flush();
sw.Close();
}


private void OnStart(string directoryToCopy, string directoryDestination,
bool overWriteFiles)
{
try
{
this.thisBackupDateTime = DateTime.Now;
fs = new FileStream(BackupLogFileName, FileMode.OpenOrCreate);
sw = new StreamWriter(fs);

//Create a Directory object using DirectoryInfo
DirectoryInfo directoryToCopyInfo = new DirectoryInfo(directoryToCopy);

//Pass the Directory for displaying the contents
getDirsFiles(directoryToCopyInfo, directoryDestination, overWriteFiles);

}
finally
{
// Update Backup Time
this.UpdateLastBackup();

sw.Flush();
sw.Close();
fs.Close();
}

}


private void getDirsFiles(DirectoryInfo singledirectory, string
directoryDestination, bool overWriteFiles)
{
//create an array of files using FileInfo object
FileInfo[] files;

try
{
//get all files for the current directory
files = singledirectory.GetFiles();

//iterate through the directory and print the files
foreach (FileInfo file in files)
{
try
{
this.CopyFile(file.FullName, directoryDestination + @"\" +
singledirectory.FullName.Substring(3), overWriteFiles);
}
catch (Exception subdirectoryFileCopyException)
{
sw.WriteLine("Problem with copying file " + file.FullName + "." +
Environment.NewLine + "Error: " + subdirectoryFileCopyException.StackTrace +
Environment.NewLine + subdirectoryFileCopyException.Message);
sw.WriteLine("");
continue;
}
}
}
catch (Exception e)
{
sw.WriteLine("Problem with " + singledirectory + ". " +
Environment.NewLine + "Error: " + e.StackTrace + Environment.NewLine +
e.Message);
sw.WriteLine("");

}
try
{
//get sub-folders for the current directory
DirectoryInfo[] dirs = singledirectory.GetDirectories();

//This is the code that calls
//the getDirsFiles (calls itself recursively)
//This is also the stopping point
//(End Condition) for this recursion function
//as it loops through until
//reaches the child folder and then stops.
foreach (DirectoryInfo dir in dirs)
{
getDirsFiles(dir, directoryDestination, overWriteFiles);
}
}
catch (Exception e)
{
sw.WriteLine("Problem reading next directories. " + Environment.NewLine
+ "Error: " + e.StackTrace + Environment.NewLine + e.Message);
sw.WriteLine("");

}

}


private void CopyFile(string sourceFileAndDirectory, string
destinationDirectoryName, bool overwrite)
{
try
{
FileInfo sourceFileInfo = new FileInfo(sourceFileAndDirectory);

// Only copy files that have been modified since the last backup
if (sourceFileInfo.LastWriteTime > this.LastBackupDateTime)
{
// NOte, Future versions should probably pass this in as a parameter or
be set from a configuration file
Console.WriteLine("Copying " + sourceFileAndDirectory);
string destination =
(this.CheckAndCreateDirectory(destinationDirectoryName) + new
FileInfo(sourceFileAndDirectory).Name);
File.Copy(sourceFileAndDirectory, destination, overwrite);
}
else
{
// Note, uncommenting this line shows files that were skipped, but
slows down the overall copy process
// Future versions should probably pass this in as a parameter or be
set from a configuration file
//Console.WriteLine("Skipping " + sourceFileAndDirectory);
}
}
catch (IOException io)
{
sw.WriteLine("Problem with " + sourceFileAndDirectory + ". " +
Environment.NewLine + "Error: " + io.StackTrace + Environment.NewLine +
io.Message);
sw.WriteLine("");
}
}

#endregion
}
}



Thanks again,
cmullins
 
cmullins said:
Sure thing. Here is the code I was using. If the code completes the
finally block is called correctly. It is only when the console is closed
while files are being copied.

Ah - I see, when the whole process is terminated abruptly. I don't
think there's anything that can be done about that. For anyone who's
interested, here's a shorter example which demonstrates the problem:

using System;
using System.IO;
using System.Threading;

class Test
{
static void Main()
{
try
{
Thread.Sleep (15000);
Console.WriteLine ("You didn't kill me!");
}
finally
{
using (StreamWriter writer = new StreamWriter
("c:\\test\\test.txt"))
{
writer.WriteLine ("Finally block");
}
}
}
}

You don't need to kill the console - just hit Ctrl-C and the process
will be killed without the finally block being executed.
 
Jon Skeet said:
Ah - I see, when the whole process is terminated abruptly. I don't
think there's anything that can be done about that. For anyone who's
interested, here's a shorter example which demonstrates the problem:

using System;
using System.IO;
using System.Threading;

class Test
{
static void Main()
{
try
{
Thread.Sleep (15000);
Console.WriteLine ("You didn't kill me!");
}
finally
{
using (StreamWriter writer = new StreamWriter
("c:\\test\\test.txt"))
{
writer.WriteLine ("Finally block");
}
}
}
}

You don't need to kill the console - just hit Ctrl-C and the process
will be killed without the finally block being executed.


That's correct, when hitting Ctrl-C the process will get killed by a call to
ProcessExit() from the default console Ctrl handler, from this point on the
CLR won't execute any manage code and forces a rude abort of the EE. However
no-one stops you from installing your own Console Ctrl handler to take care
of this,or simply refuse to terminate the process, at this point you can
simply resume the process.

Willy.
 
This is a scary situation though, even the official Microsoft documentation on this subject fails to mention this (I cannot believe they are unaware of this fact):

"A Finally block is always executed when execution leaves any part of the Try statement. No explicit action is required to execute the Finally block; when execution leaves the Try statement, the system will automatically execute the Finally block and then transfer execution to its intended destination. The Finally block is executed regardless of how execution leaves the Try statement: through the end of the Try block, through the end of a Catch block, through an Exit Try statement, through a GoTo statement, or by not handling a thrown exception"

Metallikanz!
 
Metallikanz! said:
This is a scary situation though, even the official Microsoft
documentation on this subject fails to mention this (I cannot believe
they are unaware of this fact):

"A Finally block is always executed when execution leaves any part of
the Try statement. No explicit action is required to execute the
Finally block; when execution leaves the Try statement, the system
will automatically execute the Finally block and then transfer
execution to its intended destination. The Finally block is executed
regardless of how execution leaves the Try statement: through the end
of the Try block, through the end of a Catch block, through an ExitTry
statement, through a GoTo statement, or by not handling a thrown
exception"

In this case, however, execution never actually leaves the try
statement, and certainly not in any of the ways specified above.

Note that you would never be able to make it absolutely always execute
the finally block - what would you expect to happen if the power was
turned off?
 
Back
Top