Ensuring that "finally" runs in the same thread when the thread terminates

Here is a puzzle for you guys. I have a multi-threaded program in which some threads work with managed resources, such as locks and semaphores. Some of the blocking release primitives can only be executed from the same thread on which the lock was locked.

So, here is my puzzle: I wrap these operations: try {lock-acquire ... do something} finally {lock-release}, but sometimes, when my threads finish, finally sentences are executed by the .NET garbage collection stream, and not my stream. (In a specific case, the Dispose of the object selected in the "using" statement is actually used, see below for details)

This is a little tricky to demonstrate; I see this all the time in Isis2, and I realized that this was happening by checking the flow identifiers in the acquisition and completion blocks. But I don't have a 3 line demo for you, and I'm sorry about that. I know this would make it easier to help.

Is there a way that you can delay completion for a thread until all pending completion blocks associated with this thread are executed?

---- Added information for Brand ----

What I'm really doing is related to a rather complicated self-service blocking package, which has various blocking abstractions (limited buffers, barriers, normal locks, etc.) with thread priorities and is intended for self-detection of deadlocks, priority inversions, very slow blocking and other problems. My code is quite complex and streamy enough, so I had to debug it.

An example of my basic design style is the following:

LockObject myLock = new LockObject("AnIsis2Lock"); ... using(new LockAndElevate(myLock)) { stuff } 

LockObject is a self-locking lock ... LockAndElevate notes that I locked it (in this case) and then tracks the deletion when I unlock it. Therefore, I use the fact that use should destroy a new object when the block completes, even if it throws an exception.

What I see is that quite often threads terminate, and yet the use of dispose events did not actually happen; they start later, on another thread. This only happens when one of my threads terminates; in normal execution, everything works like a charm.

So, since the translation is used to try ... finally, my question was published in terms of final sentences.

+4
source share
1 answer

So, for now, here is my best answer to my own question, based mainly on experience debugging Isis2 behavior.

If the threads do not end, "using (x = new something ()) {}" (which maps to "try {x = new something (); ...} finally {x.dispose}") works just like you expect: dispose occurs when the code is called.

But exceptions disrupt the flow of control. Therefore, if your thread throws an IsisException or something in "something" does this, control passes to catch for this exception. In this case, I am dealing with, and my catch handler is higher in the stack. C # /. NET is faced with a choice: will it catch IsisException first or will it delete first? In this case, I am sure that the system systematically throws an IsisException. As a result, the finalizer for the selected object "x" has not yet been executed, but it works and should be called soon.

[Note: for those curious, the Using statement ends with a Dispose call, but the recommended behavior for documentation is to have finalizer ~ something () {this.Dispose; } to cover all possible code paths. Dispose can be called more than once, and you should keep the flag locked against concurrency, and only remove managed resources the first time Dispose is called.]

Now, the key problem is that the finalizer, apparently, may not start before your thread has a chance to complete in this case the caught exception that terminates your thread; if not, C # will delete the object, causing dispose in the GC finalizer thread. As a result, if, as in my code, x.Dispose () opens something, an error may occur: the collection / release of the lock should occur on the same thread in .NET. So the source of potential errors for you and for me! It seems that calling GC.WaitForFinalizers in my exception handler helps in this case. It’s less clear to me whether this is a real guarantee that bad things will not happen.

Another major error in my own code was that I mistakenly caught a ThreadAbortException in multiple threads due to an old misunderstanding about how they work. Do not do this. Now I see that this creates serious problems for .NET. Just don't use Thread.Abort, ever.

So, based on this understanding, I changed Isis2, and now it works well; when my threads end, the finalizers seem to work correctly, dispose seems to happen before the thread exits (and therefore before its id is reused, which causes me confusion), and all this is good for of the world. Those who work with threads, priorities, and self-locking locks / barriers / limited buffers and semaphores should be careful: there are dragons here!

+1
source

All Articles