How does the ThreadStatic attribute work?
Asked Answered
S

5

164

How does [ThreadStatic] attribute work? I assumed that the compiler would emit some IL to stuff/retrieve the value in the TLS, but looking at a disassembly it doesn't seem to do it at that level.

As a follow up, what happens if you put it on a non-static member? We had a developer make that mistake and the compiler doesn't even proffer up a warning.

Update

Second question answered here: ThreadStatic Modified with Static C#

Solubility answered 8/3, 2011 at 2:34 Comment(2)
If the IL generated is the same (which indeed it is), then the runtime must be coded specifically to know how to allocate & read the value when it hits such a decorated field. Seems like a hack :)Supple
Note that if a ThreadStatic is used with a Thread Pool thread, the value might still be set the next time that pool thread is re-used (unless the OS guarantees that it re-initializes Thread Local Storage when a pool thread is re-used). To be safe, when starting to use a pool thread, initialize all ThreadStatics to the desired state. To make this easier, collect ThreadStatics into a MyContext class, so only one static to re-initialize: public class SomeClass { public static MyContext myContext; ... Now immediately after grabbing a pool thread, do SomeClass.myContext = new MyContext();Forgetful
B
105

The implementation semantics of thread static are below the IL level, in the .NET jit compiler. Compilers that emit to IL like VB.NET and C# don't need to know anything about Win32 TLS in order to emit IL code that can read and write a variable that has the ThreadStatic attribute. There's nothing special about the variable as far as C# knows - it's just a location to read and write stuff. The fact that it has an attribute on it is of no consequence to C#. C# only needs to know to emit IL read or write instructions for that symbol name.

The 'heavy lifting' is done by the core CLR that is responsible for making the IL work on a particular hardware architecture.

That would also explain why putting the attribute on an inappropriate (non-static) symbol doesn't get a reaction from the compiler. The compiler doesn't know what special semantics the attribute requires. Code analysis tools like FX/Cop, though, should know about it.

Another way to look at it: CIL defines a set of storage scopes: static (global) storage, member storage, and stack storage. TLS isn't on that list, very likely because TLS doesn't need to be on that list. If IL read and write instructions are sufficient to access TLS when the symbol is tagged with a TLS attribute, why should IL have any special representation or treatment for TLS? It's not needed.

Breathless answered 8/3, 2011 at 2:51 Comment(3)
But doesn't that special, implementation-specific behaviour of TLS completely subvert the "verifiable" selling-point of .NET/CLR?Venegas
I don't know anything about the design implications of this, but I find it pretty useful. I have already used it somewhere. Although, I doubt it would work on web apps using async/await, since the resuming thread (after the await) might be a different thread. I thought the [ContextStatic] attribute covered that case too (with its name implying it works based on the thread context), but I was probably wrong on my assumption. I see people online saying the [ContextStatic] atttribute is used with Remoting.Sirenasirenic
Is there any way to cover all the cases (multi-threading, web app multi-threading with async/await, etc) so that a single piece of work (e.g. a single web request, the processing of a single queue message, etc) get its own version of the variable, no matter what?Sirenasirenic
O
131

How does [ThreadStatic] attribute work?

You can think that the field marked with ThreadStatic is attached to a thread and its lifetime is comparable to the lifetime of a thread.

So in pseudocode ThreadStatic is similar (by semantics) to having a key-value attached to a thread:

Thread.Current["MyClass.myVariable"] = 1;
Thread.Current["MyClass.myVariable"] += 1;

but the syntax is just a bit easier:

class MyClass {
  [ThreadStatic]
  static int myVariable;
}
// .. then
MyClass.myVariable = 1;
MyClass.myVariable += 1;

what happens if you put it on a non-static member?

I believe it is ignored:

    class A {
        [ThreadStatic]
        public int a;
    }
    [Test]
    public void Try() {
        var a1 = new A();
        var a2 = new A();
        a1.a = 5;
        a2.a = 10;
        a1.a.Should().Be.EqualTo(5);
        a2.a.Should().Be.EqualTo(10);
    }

Additionally it is worth mentioning that ThreadStatic does not require any synchronisation mechanism as compared to normal static fields (because the state is not shared).

Odonto answered 8/3, 2011 at 2:48 Comment(2)
Second like of pseudo code should be "MyClass.myVariable", shouldn't it?Judicious
I'm not sure the exact restrictions, but I just wanted to point out if it isn't obvious that it doesn't have to be a primitive type. If you look at the source for TransactionScope they store all sorts of stuff in there for the scope (referencesource.microsoft.com/#System.Transactions/System/…)Whisk
B
105

The implementation semantics of thread static are below the IL level, in the .NET jit compiler. Compilers that emit to IL like VB.NET and C# don't need to know anything about Win32 TLS in order to emit IL code that can read and write a variable that has the ThreadStatic attribute. There's nothing special about the variable as far as C# knows - it's just a location to read and write stuff. The fact that it has an attribute on it is of no consequence to C#. C# only needs to know to emit IL read or write instructions for that symbol name.

The 'heavy lifting' is done by the core CLR that is responsible for making the IL work on a particular hardware architecture.

That would also explain why putting the attribute on an inappropriate (non-static) symbol doesn't get a reaction from the compiler. The compiler doesn't know what special semantics the attribute requires. Code analysis tools like FX/Cop, though, should know about it.

Another way to look at it: CIL defines a set of storage scopes: static (global) storage, member storage, and stack storage. TLS isn't on that list, very likely because TLS doesn't need to be on that list. If IL read and write instructions are sufficient to access TLS when the symbol is tagged with a TLS attribute, why should IL have any special representation or treatment for TLS? It's not needed.

Breathless answered 8/3, 2011 at 2:51 Comment(3)
But doesn't that special, implementation-specific behaviour of TLS completely subvert the "verifiable" selling-point of .NET/CLR?Venegas
I don't know anything about the design implications of this, but I find it pretty useful. I have already used it somewhere. Although, I doubt it would work on web apps using async/await, since the resuming thread (after the await) might be a different thread. I thought the [ContextStatic] attribute covered that case too (with its name implying it works based on the thread context), but I was probably wrong on my assumption. I see people online saying the [ContextStatic] atttribute is used with Remoting.Sirenasirenic
Is there any way to cover all the cases (multi-threading, web app multi-threading with async/await, etc) so that a single piece of work (e.g. a single web request, the processing of a single queue message, etc) get its own version of the variable, no matter what?Sirenasirenic
D
22

The [ThreadStatic] creates isolated versions of the same variable in each thread.

Example:

[ThreadStatic] public static int i; // Declaration of the variable i with ThreadStatic Attribute.

public static void Main()
{
    new Thread(() =>
    {
        for (int x = 0; x < 10; x++)
        {
            i++;
            Console.WriteLine("Thread A: {0}", i); // Uses one instance of the i variable.
        }
    }).Start();

    new Thread(() =>
   {
       for (int x = 0; x < 10; x++)
       {
           i++;
           Console.WriteLine("Thread B: {0}", i); // Uses another instance of the i variable.
       }
   }).Start();
}
Designedly answered 19/4, 2018 at 13:18 Comment(0)
R
12

The field marked with [ThreadStatic] are created on Thread Local Storage so every thread has it own copy of the field i.e the scope of the fields are local to the thread.

TLS fields are access through gs/fs segments registers.These segments are used by the OS kernels to access thread-specific memory.The .net compiler do not emit any IL to stuff/retrieve the value in the TLS. It is done by the OS kernel.

Rugg answered 21/10, 2019 at 19:6 Comment(0)
S
-1

A static field marked with ThreadStaticAttribute is not shared between threads. Each executing thread has a separate instance of the field, and independently sets and gets values for that field. If the field is accessed on a different thread, it will contain a different value.

Spies answered 3/7, 2023 at 12:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.