【问题标题】:How does the ThreadStatic attribute work?ThreadStatic 属性如何工作?
【发布时间】:2011-07-10 19:19:15
【问题描述】:

[ThreadStatic] 属性如何工作?我假设编译器会发出一些 IL 来填充/检索 TLS 中的值,但是查看反汇编时它似乎并没有在那个级别上做到这一点。

作为后续,如果你把它放在一个非静态成员上会发生什么?我们让开发人员犯了这个错误,编译器甚至没有提供警告。

更新

第二个问题在这里回答:ThreadStatic Modified with Static C#

【问题讨论】:

  • 如果生成的 IL 相同(确实如此),则必须专门对运行时进行编码,以了解如何在遇到此类修饰字段时分配和读取值。看起来像一个黑客:)
  • 请注意,如果 ThreadStatic 与线程池线程一起使用,则该值 可能 仍会在下次重新使用该池线程时设置(除非操作系统保证它在重新使用池线程时重新初始化线程本地存储)。为了安全起见,在开始使用池线程时,将所有 ThreadStatics 初始化为所需的状态。为了让这更容易,将 ThreadStatics 收集到 MyContext 类中,因此只有 一个 静态重新初始化:public class SomeClass { public static MyContext myContext; ... 现在在获取池线程后立即执行 SomeClass.myContext = new MyContext();

标签: c# static threadstatic


【解决方案1】:

[ThreadStatic]属性如何 工作吗?

你可以认为标有ThreadStatic的字段是附加到一个线程上的,它的生命周期相当于一个线程的生命周期。

所以在伪代码中ThreadStatic 类似于(在语义上)将键值附加到线程:

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

但语法更简单一些:

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

如果你把它放在一个非静态成员上会发生什么?

我相信它被忽略了:

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

另外值得一提的是,ThreadStatic 与普通静态字段相比不需要任何同步机制(因为状态不共享)。

【讨论】:

  • 第二个伪代码应该是"MyClass.myVariable",不是吗?
  • 我不确定确切的限制,但我只是想指出它是否不一定是原始类型不是很明显。如果您查看 TransactionScope 的源代码,他们会在其中存储各种内容以用于范围 (referencesource.microsoft.com/#System.Transactions/System/…)
【解决方案2】:

线程静态的实现语义低于 IL 级别,在 .NET jit 编译器中。向 IL 发出的编译器(如 VB.NET 和 C#)不需要了解有关 Win32 TLS 的任何信息,即可发出可以读取和写入具有 ThreadStatic 属性的变量的 IL 代码。就 C# 所知,该变量没有什么特别之处——它只是一个读取和写入内容的位置。它具有属性这一事实对 C# 没有影响。 C# 只需要知道为该符号名称发出 IL 读取或写入指令。

“繁重的工作”由负责使 IL 在特定硬件架构上工作的核心 CLR 完成。

这也可以解释为什么将属性放在不适当的(非静态)符号上不会得到编译器的反应。编译器不知道该属性需要什么特殊语义。不过,像 FX/Cop 这样的代码分析工具应该知道这一点。

另一种看待它的方式:CIL 定义了一组存储范围:静态(全局)存储、成员存储和堆栈存储。 TLS 不在该列表中,很可能是因为 TLS 不需要在该列表中。如果当符号带有 TLS 属性标记时,IL 读写指令足以访问 TLS,为什么 IL 应该对 TLS 有任何特殊表示或处理?不需要。

【讨论】:

  • 但是 TLS 的这种特殊的、特定于实现的行为不是完全颠覆了 .NET/CLR 的“可验证”卖点吗?
  • 我对它的设计含义一无所知,但我觉得它非常有用。我已经在某个地方使用过它。虽然,我怀疑它是否适用于使用 async/await 的 Web 应用程序,因为恢复线程(在等待之后)可能是不同的线程。我认为[ContextStatic] 属性也涵盖了这种情况(其名称暗示它基于线程上下文工作),但我的假设可能是错误的。我看到网上有人说[ContextStatic] 属性与Remoting 一起使用。
  • 有什么方法可以覆盖所有情况(多线程、Web 应用程序多线程与 async/await 等),以便单个工作(例如单个 Web 请求、处理单个队列消息等)获取自己的变量版本,无论如何?
【解决方案3】:

[ThreadStatic] 在每个线程中创建相同变量的隔离版本。

例子:

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

【讨论】:

    【解决方案4】:

    标有[ThreadStatic] 的字段是在线程本地存储上创建的,因此每个线程都有自己的字段副本,即字段的范围是线程本地的。

    TLS 字段是通过 gs/fs 段寄存器访问的。OS 内核使用这些段来访问线程特定的内存。.net 编译器不会发出任何 IL 来填充/检索 TLS 中的值。它由操作系统内核完成。

    【讨论】:

      猜你喜欢
      • 2013-08-22
      • 2010-12-22
      • 2016-03-02
      • 2011-02-10
      • 2012-04-22
      • 1970-01-01
      • 1970-01-01
      • 2010-09-29
      • 2013-01-30
      相关资源
      最近更新 更多