【发布时间】:2017-08-09 10:01:18
【问题描述】:
我想了解在 .NET 中使用并行处理时 ThreadStatic 数据何时被清除。
考虑以下(大幅缩减的)代码:
我的上下文类
public class AppContext
{
[ThreadStatic]
private static Person _person;
public Person Shopper
{
get => AppContext._person;
set => AppContext._person = value;
}
}
- 这个类会有很多属性 - 肯定超过 50 个。
- 每个属性的支持字段都是 ThreadStatic 字段。
大量对象的并行处理
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 最初为 null。
当该线程 (managedThreadId = 24) 初始化 Shopper 时,该 Shopper 被保存在 ThreadStatic 字段中,因此其他线程无法访问。
下一次同一个线程 (managedThreadId = 24) 重新进入循环(以处理不同的项目)时,singleContext.Shopper 仍然是在前一个循环期间实例化的同一个对象。
所以,我的理解(如有错误请指正)是:
- 当我们创建一个 Parallel.ForEach 循环时,它会从线程池中分配一些线程
- 当其中一个线程完成一个循环,然后开始一个新循环时,它的堆栈不会被清理 - 在第一次迭代中设置的所有 ThreadStatic 变量都保留在第二次迭代中(尽管我们重写了它们)
- 只有当 Parallel.ForEach 完成时,线程才会返回到线程池中
因此,我的问题是:这些属性何时从内存中删除?当线程返回到 ThreadPool 时(大概是在 Parallel.ForEach 完成时),或者稍后将线程分配给不同的 AppDomain 时,它们是否被清理?我问是因为其中一些属性可能会消耗大量内存,我想确保它们不会占用内存超过他们需要的时间。我不是特别喜欢在每次循环迭代结束时将它们显式设置为 null 的想法...
【问题讨论】:
-
我不确定
AppContext类是为了解决什么问题,但我希望让一切都更加本地化,并考虑ThreadLocal<T>而不是 @987654325 @。您可以在使用后明确Dispose(或将其包装在using中),然后您将清楚地了解生命周期。 -
@Damien_The_Unbeliever:AppContext 类包含开始处理项目所需的所有信息。如果它是一个网站,它可能是关于购物者或网站本身的信息。这是获取常用数据的“一站式”商店。我想它也有点像缓存,但在架构上有所不同。
-
@Damien_The_Unbeliever:关于使用 ThreadLocal
的要点很好,我们可能会在稍后讨论。为了尽量减少重构,我们需要保留单个 AppContext 的概念,但让每个属性的支持字段都有一个 ThreadLocal 。这需要我们在 Parallel 循环中调用 .Dispose() 来处理当前线程的值。 -
无论我们使用的是 ThreadStatic 还是 ThreadLocal
,并行循环的每个后续迭代仍然保留该托管线程的上一次迭代的值。我对这些何时从内存中释放的理解仍然不清楚。 -
我在您发布的一小段代码中没有看到任何证明使用
[ThreadStatic]、ThreadLocal<T>或AsyncLocal<T>中的any 是合理的。对于瞬态线程,通常正确的方法是将适当的上下文对象传递给线程,或者在这样的对象中实现线程(即隐式传递this)。所有这些其他机制都更重,并且具有这里似乎不需要的语义。不幸的是,您提出的问题过于宽泛,缺乏足够的背景信息,任何人都无法猜测您真正需要的答案。
标签: c# multithreading parallel-processing task-parallel-library