Click here to Skip to main content
15,063,596 members
Please Sign up or sign in to vote.
4.00/5 (1 vote)
Hi guys.

Yesterday a junior developer from my department in our firm provided me with code, and asked me a question.
I know that the code snippet has a lot of violations, I've pointed them to the junior:

  • improper implementation of the Dispose pattern
  • usage of managed objects in .NET finalizators, and so on.

Let's throw those things (the implementation) out of this discussion and concentrate on the received result.

Please, look at the code snippet below and if your honor, try to explain why it happened so.
C#
public class FileHandler
	{
		private StreamReader sr;
		private string _fname;
 
		public FileHandler(string filename)
		{
			sr = new StreamReader(filename);
		}
 
		public void PerformRead()
		{
			string line = null;
			while ((line = sr.ReadLine()) != null)
			{
				if (line.Contains("declare"))
					line = line.Trim();
			}
		}
 
		public void Close()
		{
			sr.Close();
		}
	}
 
	public sealed class FileReader
	{
		private FileHandler fileHandler;
 
		public FileReader(string fileName)
		{
			fileHandler = new FileHandler(fileName);
		}
 
		public void Read()
		{
			FileTools.ReadFile(fileHandler);
		}
 
		~FileReader()
		{
			Console.WriteLine("Closing vie finalization");
			fileHandler.Close();
		}
 
		public void Dispose()
		{
			fileHandler.Close();
			GC.SuppressFinalize(this);
		}
	}
 
	public static class FileTools
	{
		public static void ReadFile(FileHandler fileHandler)
		{
			if (fileHandler != null)
				fileHandler.PerformRead();
		}
	}
	
	class Program
	{
		static void Main(string[] args)
		{
			FileReader fr = new FileReader("myfile.txt");
			fr.Read();
		}
	}

NOTE: The issue is only reproduced in the Release mode (not under VS, when we run a standalone application *.exe) and the next crucial obstacle here is that the file which we are processing must be more than 3 Mb.
My Question is:
Why FileReader is being garbage collected (reach finalization, moved to the Freachable queue and being leaned there) before the PerformRead method is being completed?
As a result, when handling a large file, we receive NullReferenceException before PerformRead is complete.
Under VS we can't observe the behavior described above.
See exception link Error printscreen[^]
Posted
Updated 27-Feb-15 0:32am
v6
Comments
[no name] 27-Feb-15 4:03am
   
At which point does the NullReferenceException occur? I could imagine that in release mode something got optimized away because PerformRead() doesn't return anything and doesn't modify some value from an outer scope.
Oleksandr Kulchytskyi 27-Feb-15 4:15am
   
Hi, see the error exception image http://imgur.com/LzXsJNq
Issue happens during reading lines in PerformRead method and the main reason why, this is because the finalization of FileReader took place.
As a result StreamReader object has been closed.
[no name] 27-Feb-15 4:35am
   
I suggest try running the application in release mode but with optimizations turned off. If the exception isn't being thrown then, turn optimizations on again but decorate FileReader.Read() with a [MethodImplAttribute(MethodImplOptions.NoInlining)] attribute.
Oleksandr Kulchytskyi 27-Feb-15 5:32am
   
Yep, you are completely right!
The main showstopper is compile optimization during release mode.
In such case , since fr.Read(); is returned void , CLR treated FileReader fr as a garbage and finalize it.
Oleksandr Kulchytskyi 27-Feb-15 5:32am
   
Thanks!
But to be honest [MethodImplAttribute(MethodImplOptions.NoInlining)]
does't help.
[no name] 27-Feb-15 6:05am
   
Glad I could help. But the question would remain what kind of optimization is responsible for the possibility of finalization. My next best guess would be tail-call-optimization. If you're inclined to investigate further, I'd suggest inserting a Console.WriteLine("end"); after the fr.Read() in Main() and running again with optimization.
Oleksandr Kulchytskyi 27-Feb-15 7:06am
   
Ok, thank you very much for the help.
I appreciate it!
Will be needed to extend my knowledge in JIT optimization.

1 solution

So with help of @manchanx
we reached next steps:
1) The main culprit who has been released FileReader object and put it to the finalization stage was optimization that is turned on during Release and debug symbols are not emitted.
That is why during the active workflow in the PerformRead method and at first glance preserving all references alive , the GC treated FileReader fr object as a garbage
and submit it to the finalization stage.

So to eliminate such issue there are 3 steps:
1) Apply [MethodImplAttribute(MethodImplOptions.NoInlining)] to both Main and Read methods.
2) Operate fr reference as much as possible: for example add next lines:
C#
class Program
	{
		static void Main(string[] args)
		{
			FileReader fr = new FileReader("myfile.txt");
			fr.Read();
                        Console.WriteLine(fr.GetType());
		}
	}


3) Add next setting file [assemblyname].ini
With these settings, your app runs at full speed. When you want to debug your app by turning on debug tracking and possibly turning off (CIL) code optimization, just use the following settings:
The content of a file is next:

[.NET Framework Debugging Control]
GenerateTrackingInfo=1
AllowOptimize=0
   

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)




CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900