This interesting question thanks to the Assembly head Friendship offers.
Take a look at the following code:
Public class Dummy { publicstatic Dummy Instance; Public int 1 ; ~Dummy () { this; } }
The call is made by the following code (where I adjust the output log):
Task.run (() =>{ var d = new Dummy (); D = null ; Gc. Collect (); Gc. WaitForFullGCComplete ();}). Wait (); var isNull = dummy.instance = null ; Console.WriteLine (isNull); if (false == IsNull) {Console.WriteLine ( dummy.instance.x);} else {Console.WriteLine ( " oh no! Dummy.instance is null. "
question : instance = = NULL for the above output is true or false?
Here you can stop reading the analysis below and think about what your answer will be.
First of all, the question is the kind of feeling that you know there's a pit to get you into, but you may have to get into it first. In particular, task, GC, static fields, instance fields, destructors so many things mixed together, a look on and multithreading has a relationship, quite confusing, right?
The first time I saw it, I thought the task was running for GC recycling and then wait until the task ended, and the variable D pointed to the object because of the GC. WaitForFullGCComplete () This line, should have been garbage collected successfully, when the destructor is executed, the static variable instance the current object pointed to this (that is, the reference object that the variable d is pointing at the beginning) should be null, Then Instance==null must return True. Or the output should always be a definite value.
However, the actual performance is not always the case, please note that after my personal experiments, many times (greater than or equal to 1 is less than or equal to 50000), the output of true and false number of times is indeterminate, but the probability of true is significantly more than the total number of false,false seems always between 1 to 10.
To prevent some optimizations of the C # compiler, compare the results of release and debug respectively, the result is the same.
And then there's a little bit of a reason why there are two output results. Loop experiment The following code, there is no task interference, but the effect and has a task to run the same, there is a true or false output, that is, no task order to execute GC code is also a different output.
var d = new Dummy ();d = null var isNull = dummy.instance = null ; Console.WriteLine (isNull); if (false == IsNull) {Console.WriteLine ( dummy.instance.x);} else {Console.WriteLine ( " oh no! Dummy.instance is null. "
Recently I was just re-learning GC, and recently just summed up the GC knowledge, think of the end of the destructor has "extended" garbage object life cycle, but also can not say. Have you ever thought about whether the destructor has been specially optimized for static fields, such as instance the GC recycle policy after the assignment of the G0, adjusting the generation of the G1 to the generation, or the this does not automatically recycle when the destructor is executed? That is, static field assignment the control of the thread security causes the this to be assigned to instance and then this instance is reclaimed to be empty, but because instance is a static field, it is the root of the GC, so, eh? Learn a lot of theories and find that practice is still not the case.
Could not think of the root cause, consulted the next head, he briefly replied that "the actual condition of the race is the finalizer execution of the thread." ".
destructor race condition, Finalizer, thread? Oh, wait, wait, the main thread, the thread pool managed by the current task run, the GC thread, the finalizer thread, What are the competing conditions that produce competition between several threads (such as GC threads and finalizer threads) or between threads of the same type (such as finalizer threads and finalizer threads)?
Along this line of thought, print out the thread ID and compare it to the conclusion?
Serious statement : Here I do not know when the execution of the destructor ~dummy () when the front thread is the finalizer thread, reading as if it is the meaning, but did not give the code, this article first temporarily with the finalizer thread so name this thread. If you know how to get the GC thread and the finalizer thread correctly, please do not advise.
Do it now, tweak the code, print out a few more logs, although the printed log is a bit messy, but finally you can be sure that the task and destructor execute the different managed thread ID, and that the thread ID of the managed thread inside the destructor is always the same .
Public classDummy { Public StaticDummy Instance; Public intX =1; Public Staticconcurrentbag<int> threadidbag =Newconcurrentbag<int>(); ~Dummy () {varThreadId =Thread.CurrentThread.ManagedThreadId; Console.WriteLine ("destructor CurrentContext thredid:{0}", threadId); if(Threadidbag.contains (threadId) = =false) {threadidbag.add (threadId); } Instance= This; //Console.WriteLine ("Destructor===instance is null:{0}", Instance = = null); } }
Dummy
The calling code is as follows:
Static voidMain (string[] args) { varCounter =0;//Statistics Dummy Instance is not null count vartestcnt =1;//50000;//Execute Task Count while(Testcnt >0) {testcnt--; Task.run (()= { varD =NewDummy (); D=NULL; Gc. Collect (); Gc. WaitForFullGCComplete (); Console.WriteLine ("Task currentcontext thredid:{0}", Thread.CurrentThread.ManagedThreadId); }). Wait (); varIsNull = Dummy.instance = =NULL; Console.WriteLine (IsNull); if(false==isNull) {Console.WriteLine (dummy.instance.x); Counter++; } Else{Console.WriteLine ("Oh no! Dummy Instance is null."); } Console.WriteLine ("========================"); } thread.sleep ( -); Console.WriteLine ("End Task ..."); Console.WriteLine ("Dummy Instance is not null counter:{0}", counter); Console.WriteLine ("Finalizer ThreadID count:{0}", Dummy.threadIDBag.Count);//the output here is 1Console.readkey ();}
RunTask
Here, I'm sure the "race condition" of the assembly head is certainly not a race between the finalizer thread and the finalizer thread, nor the race between the GC thread and the finalizer thread.
And because the head said that the task ran after wait, it should not be the race between the managed thread that the task runs and the finalizer thread.
Therefore, there should be a thread contention between the execution of the calling thread (the main thread that calls Console.WriteLine () after the task has been executed, and the finalizer thread.
To the conclusion I can conclude, I think the explanation that might make sense is that when the application execution thread mainthread run code Console.WriteLine (dummy.instance = = null), The destructor thread finalizerthread may have just been executed but has not run instance=this this line of code so that dummy.instance is not empty and the output is false.
The simple understanding is that the execution uncertainty of the finalizer thread causes the output to have different effects.
I don't know if you think so.
Add three questions:
1. If Gc.waitforfullgccomplete () is changed to GC. WaitForPendingFinalizers () What is the output effect?
2. If dummy inherits from IDisposable, what is the thread ID of the Execute Dispose () method?
3. How to get GC thread and finalizer thread directly and correctly? Are they all managed threads in the thread pool?
See more and want to do it again, practice the truth.
Reference:
<<CLR Via c#>>
Http://www.cppblog.com/Solstice/archive/2010/01/28/dtor_meets_threads.html
Http://msdn.microsoft.com/zh-cn/library/system.idisposable.dispose%28v=vs.110%29.aspx
Http://blogs.msdn.com/b/dotnet/archive/2014/11/12/net-core-is-open-source.aspx
An interesting question about GC and destructor