Como o atributo ThreadStatic funciona?

138

Como o [ThreadStatic]atributo funciona? Presumi que o compilador emitisse algum IL para armazenar / recuperar o valor no TLS, mas olhando para uma desmontagem, não parece fazê-lo nesse nível.

Como acompanhamento, o que acontece se você colocá-lo em um membro não estático? Tivemos um desenvolvedor cometendo esse erro e o compilador nem sequer emitiu um aviso.

Atualizar

Segunda pergunta respondida aqui: ThreadStatic modificado com C # estático

joshperry
fonte
1
Se a IL gerada for a mesma (o que realmente é), o tempo de execução deverá ser codificado especificamente para saber como alocar e ler o valor quando atingir um campo decorado. Parece que um hack :)
Rex M

Respostas:

92

A semântica de implementação do encadeamento estático está abaixo do nível IL, no compilador .NET jit. Compiladores que emitem para IL como VB.NET e C # não precisam saber nada sobre o Win32 TLS para emitir código IL que pode ler e gravar uma variável que possui o atributo ThreadStatic. Não há nada de especial na variável, tanto quanto o C # sabe - é apenas um local para ler e escrever coisas. O fato de ele ter um atributo não tem importância para o C #. O C # só precisa saber para emitir instruções de leitura ou gravação de IL para esse nome de símbolo.

O 'trabalho pesado' é feito pelo CLR principal, responsável por fazer a IL funcionar em uma arquitetura de hardware específica.

Isso também explicaria por que colocar o atributo em um símbolo inapropriado (não estático) não recebe uma reação do compilador. O compilador não sabe que semântica especial o atributo requer. Ferramentas de análise de código como FX / Cop, no entanto, devem saber sobre isso.

Outra maneira de analisar: o CIL define um conjunto de escopos de armazenamento: armazenamento estático (global), armazenamento de membros e armazenamento de pilha. O TLS não está nessa lista, muito provavelmente porque o TLS não precisa estar nessa lista. Se as instruções de leitura e gravação de IL são suficientes para acessar o TLS quando o símbolo é marcado com um atributo TLS, por que o IL deve ter alguma representação ou tratamento especial para o TLS? Não é necessário.

dthorpe
fonte
Mas esse comportamento especial e específico da implementação do TLS não subverte completamente o ponto de venda "verificável" do .NET / CLR?
Dai
116

Como o atributo [ThreadStatic] funciona?

Você pode pensar que o campo marcado com ThreadStatic está anexado a um encadeamento e seu tempo de vida é comparável ao tempo de vida de um encadeamento.

Portanto, no pseudocódigo ThreadStaticé semelhante (por semântica) a ter um valor-chave anexado a um thread:

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

mas a sintaxe é um pouco mais fácil:

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

o que acontece se você colocá-lo em um membro não estático?

Eu acredito que é ignorado:

    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);
    }

Além disso, vale ressaltar que ThreadStaticnão requer nenhum mecanismo de sincronização em comparação com os campos estáticos normais (porque o estado não é compartilhado).

Dmytrii Nagirniak
fonte
1
O segundo tipo de pseudo-código deveria ser "MyClass.myVariable", não deveria?
precisa saber é o seguinte
Não tenho certeza das restrições exatas, mas só queria salientar se não é óbvio que não precisa ser do tipo primitivo. Se você olhar para a fonte para TransactionScopeeles armazenam todos os tipos de coisas lá para o escopo ( referencesource.microsoft.com/#System.Transactions/System/... )
Simon_Weaver
10

O [ThreadStatic] cria versões isoladas da mesma variável em cada thread.

Exemplo:

[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();
}
Rui Ruivo
fonte
3

Os campos marcados com [ThreadStatic]são criados no armazenamento local do encadeamento, para que cada encadeamento tenha sua própria cópia do campo, ou seja, o escopo dos campos é local para o encadeamento.

Os campos TLS são acessados ​​por registradores de segmentos gs / fs. Esses segmentos são usados ​​pelos kernels do SO para acessar a memória específica do encadeamento. O compilador .net não emite nenhuma IL para encher / recuperar o valor no TLS. Isso é feito pelo kernel do SO.

Arif H-Shigri
fonte