C# 并行:线程静态属性的生存期(.NET)

C# 并行:线程静态属性的生存期(.NET),c#,multithreading,parallel-processing,task-parallel-library,C#,Multithreading,Parallel Processing,Task Parallel Library,我想了解在.NET中使用并行处理时,ThreadStatic数据何时被清除 考虑以下(大幅缩减)代码: 我的上下文类 public class AppContext { [ThreadStatic] private static Person _person; public Person Shopper { get => AppContext._person; set => AppContext._person = v

我想了解在.NET中使用并行处理时,ThreadStatic数据何时被清除

考虑以下(大幅缩减)代码:

我的上下文类

public class AppContext
{
    [ThreadStatic]
    private static Person _person;

    public Person Shopper
    {
        get => AppContext._person;
        set => AppContext._person = value;
    }
}
var response = await Get100ItemsToProcess().ConfigureAwait(false);
var singleContext = new AppContext();

Parallel.ForEach(response.items, new ParallelOptions(), i =>
{
    // Set all properties on the singleContext for this thread.
    singleContext.Shopper = new Shopper { Name = ...., etc}
    ....

    ProcessItem(i);

    // Dispose of any IDisposable properties on the singleContext
    ....
});
  • 这个类会有很多属性,当然超过50个
  • 每个属性的支持字段都是ThreadStatic字段
大量对象的并行处理

public class AppContext
{
    [ThreadStatic]
    private static Person _person;

    public Person Shopper
    {
        get => AppContext._person;
        set => AppContext._person = value;
    }
}
var response = await Get100ItemsToProcess().ConfigureAwait(false);
var singleContext = new AppContext();

Parallel.ForEach(response.items, new ParallelOptions(), i =>
{
    // Set all properties on the singleContext for this thread.
    singleContext.Shopper = new Shopper { Name = ...., etc}
    ....

    ProcessItem(i);

    // Dispose of any IDisposable properties on the singleContext
    ....
});
注:

  • ProcessItem(…)不是一个简单的函数,而是一个复杂、多步骤、同步的过程,几乎是“应用程序中的应用程序”。因为它是同步的,所以我们可以使用ThreadStatic属性来保存特定于正在处理的项的数据

  • 当线程(例如managedThreadId=24)第一次进入并行循环时,singleContext.Shopper最初为空

  • 当该线程(managedThreadId=24)初始化购物者时,该购物者保存在ThreadStatic字段中,因此其他线程无法访问

  • 下次同一线程(managedThreadId=24)重新进入循环(以处理不同的项目)时,singleContext.Shopper仍然是上一个循环中实例化的同一对象

因此,我的理解(如果错误,请更正)是:

  • 当我们创建Parallel.ForEach循环时,会从线程池中为它分配一些线程
  • 当其中一个线程完成了一个循环,然后启动了一个新的循环时,它的堆栈不会被清除——在第一次迭代中设置的所有ThreadStatic变量都会保留在第二次迭代中(尽管我们重写了它们)
  • 只有当Parallel.ForEach完成时,线程才会返回到线程池

因此,我的问题是:这些属性何时从内存中删除?当线程返回到线程池时(可能是Parallel.ForEach完成时),或者稍后当线程被分配到不同的AppDomain时,它们是否被清除?我这样问是因为这些属性中的一些可能会消耗大量内存,我想确保它们不会占用内存超过需要的时间。我并不特别喜欢在每次循环迭代结束时将它们显式设置为null的想法…

ThreadStatic
是一个特定于线程的存储空间

线程静态属性的生存期


ThreadStatic
是线程特定的存储空间,因此
ThreadStatic
属性对象的生存期就是线程的生存期

当我们创建Parallel.ForEach循环时,会从线程池中为它分配一些线程

并行比这更复杂。在执行过程中,它可以根据需要调整使用的线程数。线程可以在并行循环运行时“进入”和“离开”并行循环的“所有权”

当其中一个线程完成一个循环,然后启动一个新的循环时,它的堆栈不会被清除——在第一次迭代中设置的所有ThreadStatic变量都会保留在第二次迭代中

对。它与“堆栈”无关
ThreadStatic
是特定于线程的存储空间,因此它仍然与线程相关

何时从内存中删除这些属性


ThreadStatic
是一个特定于线程的存储空间,因此当线程完成时,它们会被清除

当线程返回到线程池时(大概在Parallel.ForEach完成时),它们是否被清除

否。线程仍然处于活动状态,因此
ThreadStatic
属性对象也仍然处于活动状态

我并不特别喜欢在每次循环迭代结束时将它们显式设置为null


Peter Duniho说得对:“在你发布的一点点代码中,我没有看到任何东西可以证明使用
[ThreadStatic]是正确的
ThreadLocal
AsyncLocal
。对于临时线程,通常正确的方法是只将适当的上下文对象传递给线程……所有这些其他机制都更重,并且具有这里似乎不需要的语义。”

我不确定
AppContext
类是什么问题的解决方案的一部分,但我希望让所有内容都更本地化,并考虑
ThreadLocal
而不是
ThreadStatic
。您可以显式地
Dispose
,在使用后(或使用
将其包装在
中),然后您就可以清楚地了解其使用寿命。@Damien\u不相信者:AppContext类保存开始处理该项所需的所有信息。如果它是一个网站,它可能是关于购物者或网站本身的信息。它是获取常用数据的“一站式”商店。我想它也有点像缓存,但在架构上有所不同。@Damien_The_Unsiever:关于ThreadLocal的使用,这是一个很好的观点,我们以后可能会讨论这个问题。为了模拟重构,我们需要保留单个AppContext的概念,但每个属性的支持字段都有一个ThreadLocal。这将需要我们在并行循环中调用.Dispose()来处理当前线程的值。无论我们使用的是ThreadStatic还是ThreadLocal,并行循环的每个后续迭代都会保留该托管线程上一次迭代的值。我对这些从内存中释放出来的时间的理解仍然不清楚。我在您发布的一小段代码中没有看到任何可以证明使用
[ThreadStatic]
ThreadLocal
asynchlocal
的理由。对于瞬态线程,通常正确的方法是只将适当的上下文对象传递给线程,或者在这样的对象中实现线程(即,因此
是隐式传递的)。所有这些都是其他机制