3

I'm testing the below code:

private static void Main()
{
    var t = new Thread(() => 
    {
        var m = new MyCls();
        m.DoWork();       
    });
    t.Start();

    // simulate time-consuming job
    Thread.Sleep(1000);

    t.Abort();
    Console.Write("\nDone");
}


public class MyCls
{
    static MyCls()
    {
        for (int i = 0; i < 10; i++)
        {
            Console.Write(i);
            Thread.Sleep(1000);
        } 
    }

    public void DoWork()
    {
        Console.WriteLine("executing DoWork..");
    }
}

And the output I get is:

0123456789
Done

It seems that t.Abort() call is blocking the main thread until the execution of the static constructor finishes, and according to the documentation:

The thread that calls Abort might block if the thread that is being aborted is in a protected region of code, such as a catch block, finally block, or constrained execution region.

My questions is:

  1. Do static constructors really run as constrained execution regions (CERs) ?
  2. If yes, what other code blocks that run as CERs ?
KeyBored
  • 601
  • 3
  • 13
  • Please be aware that `Thread.Abort()` can corrupt the run-time. The only safe time to call abort is when you're trying to crash out of your application entirely - i.e. aborting all threads. – Enigmativity Oct 18 '19 at 23:58
  • @Enigmativity Yes I'm aware that `Thread.Abort()` is a bad practice, I'm just trying to learn how the CLR behaves in some situations. – KeyBored Oct 19 '19 at 00:16

2 Answers2

1

It seems that static constructors are guaranteed to have completed, almost.

Static constructors can be called explicitly with RuntimeHelpers.RunClassConstructor which 'Ensures that the type initializer (also known as a static constructor) for the specified type has been run'.

In your example, the code could be written as follows.

var t = new Thread(() =>
{
  System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(MyCls).TypeHandle);
  var m = new MyCls();
});

RunClassConstructor calls _RunClassConstructor which is annotated with the following comment.

In the absence of class constructor deadlock conditions, the call is further guaranteed to have completed.

// RunClassConstructor causes the class constructor for the given type to be triggered
// in the current domain.  After this call returns, the class constructor is guaranteed to
// have at least been started by some thread.  In the absence of class constructor
// deadlock conditions, the call is further guaranteed to have completed.
//
// This call will generate an exception if the specified class constructor threw an 
// exception when it ran. 
[System.Security.SecuritySafeCritical]
[ResourceExposure(ResourceScope.None)]
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static extern void _RunClassConstructor(RuntimeType type);
tymtam
  • 20,472
  • 3
  • 58
  • 92
0

Update: The question is about t.Abort() blocking, not interrupting, so this answer is hardly relevant :)


The documentation for Thread.Abort Method explicitly states that Abort may abort a static constructor.

When a thread calls Abort on itself, the effect is similar to throwing an exception; the ThreadAbortException happens immediately, and the result is predictable. However, if one thread calls Abort on another thread, the abort interrupts whatever code is running. There is also a chance that a static constructor could be aborted. In rare cases, this might prevent instances of that class from being created in that application domain.

and

For example, calling Thread.Abort may prevent static constructors from executing or prevent the release of unmanaged resources.


While your example elegantly shows that a static constructor may not be interrupted, there is no guarantee for this behaviour.


UPDATE

Here is an example where the static ctor is interrupted with Abort().

Code

public static void Main()
{
    Console.WriteLine($"START");
    var t = new Thread(() =>
    {
        var m = new MyCls();
    });
    Console.WriteLine($"t.Start");
    t.Start();

    Thread.Sleep(2000);
    Console.WriteLine($"Trying to create a new object");

    try
    {
        var m2 = new MyCls();

    }
    catch (Exception ex) { Console.WriteLine(ex); }
    Console.WriteLine("All done");
    Console.ReadLine();
}


public class MyCls
{
    static MyCls()
    {
        for (int i = 0; i < 10; i++)
        {
            if (i == 4)
            {
                Console.WriteLine($"sctor calling Abort on itself");
                Thread.CurrentThread.Abort();
            };
            Console.WriteLine($"sctor.{i}");
            Thread.Sleep(100);
        }
    }
}

Output

START
t.Start
sctor.0
sctor.1
sctor.2
sctor.3
sctor calling Abort on itself
Trying to create a new object
System.TypeInitializationException: The type initializer for 'MyCls' threw an exception. ---> System.Threading.ThreadAbortException: Thread was being aborted.
   at System.Threading.Thread.AbortInternal()
   at System.Threading.Thread.Abort()
   at Program.MyCls..cctor() in c:\users\letss\source\repos\ConsoleApp2\ConsoleApp2\Program.cs:line 42
   --- End of inner exception stack trace ---
   at Program.MyCls..ctor()
   at Program.Main() in c:\users\letss\source\repos\ConsoleApp2\ConsoleApp2\Program.cs:line 21
All done
tymtam
  • 20,472
  • 3
  • 58
  • 92
  • My example doesn't just show that a static constructor may not be interrupted, it also shows that the calling thread is blocked, and the case where `Thread.Abort()` blocks the calling thread is likely to happen (only?) with _protected regions of code_. – KeyBored Oct 19 '19 at 00:15
  • 1
    Got ya. You're more about the blocking rather than aborting. – tymtam Oct 19 '19 at 00:40