Would be nice if this article could call out thread-safety aspects of static constructors. Thanks!
⚠Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.
/cc @jcouv to check this answer.
My interpretation of the C# spec section on static constructors, it states:
The static constructor for a closed class type executes at most once in a given application domain.
That would seem to imply that only thread will invoke the static constructor.
However, there are no restrictions on the code you write inside a static constructor. You could write code that starts new threads in the constructor. That would require your own thread synchronization.
Yeah, this is a similar discussion #10244, there's always unsafe things you can do anywhere in code, but it's always nice to know when you don't need an extra layer of mutexing around things or not or can use the inherit constructs in the language. :)
I'm not quite sure how you want to express this in this documentation, but C# static class constructors are thread-safe, and executed under a lock that is specific to the exact specific type of the class. (This is important for generics, i.e. the List
The precise behavior of the thread-safety of class constructors can be found in sections 10.5.3.1 and 10.5.3.3 in partition II of the ECMA 335 standard. See http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf
Here is a short, somewhat more easily readable description of the special behavior of the class constructor lock. For exact details see section 105.3.3 as referenced above.
This lock is a deadlock aware lock that will not deadlock if the class constructor for type A depends on the class constructor for type B, which in turn depends on the class constructor for type A. In that case, the class constructor lock will allow the class constructor for type B to observe the partially uninitialized state of type A, and would also allow type A to observe the partially initialized state of type B if initialization started with type B. This deadlock avoidance mechanism is robust, and works even in cases where the initialization of types A and B started on separate threads, but reasoning about the correctness of such class constructors is difficult, such that it is recommended that circular references in static constructors are to be avoided as a matter of good software engineering practice.
Standard thread synchronization primitives such as monitors, semaphores, mutexes, etc, do not participate in the deadlock avoidance scheme, and it is possible to build a deadlock in their presence, if they are not used in a safe manner.
I'll propose the following text based on the above comments:
The runtime calls a static constructor no more than once in a single application domain. Furthermore, that call is made in a locked region based on the specific type of the class. No additional locking mechanisms are needed in the body of a static constructor.
Please vote up or down, with additional suggestions.
As it's executed under a lock, might also be worth noting what to avoid:
So, to avoid the risk of deadlocks, avoid blocking the current thread in static constructors and initializers: don’t wait on tasks, threads, wait handles or events, don’t acquire locks, and don’t execute blocking parallel operations like parallel loops, Parallel.Invoke and PLINQ queries.
Most helpful comment
I'll propose the following text based on the above comments:
Please vote up or down, with additional suggestions.