并行:ThreadStatic 属性的生命周期 (.NET)
Parallel: lifetime of ThreadStatic properties (.NET)
我想了解在 .NET 中使用并行处理时 ThreadStatic 数据何时被清除。
考虑以下(大幅缩减)代码:
我的背景class
public class AppContext
{
[ThreadStatic]
private static Person _person;
public Person Shopper
{
get => AppContext._person;
set => AppContext._person = value;
}
}
- 这个 class 会有很多属性 - 当然超过 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(...) 不是一个简单的函数,而是一个复杂的、多步骤的、同步的过程,几乎是一个 "application within an application"。因为它是同步的,所以我们可以使用 ThreadStatic 属性来保存特定于正在处理的项目的数据。
当线程(例如managedThreadId = 24)第一次进入并行循环时,那么singleContext.Shopper初始为null。
当该线程 (managedThreadId = 24) 初始化购物者时,该购物者被保存在 ThreadStatic 字段中,因此其他线程无法访问。
下一次同一个线程(managedThreadId = 24)重新进入循环(处理不同的项目),那么singleContext.Shopper仍然是在上一个循环。
所以,我的理解(如有错误请指正)是:
- 当我们创建一个 Parallel.ForEach 循环时,它会从线程池
中分配一些线程
- 当其中一个线程完成一个循环,然后开始一个新循环时,它的堆栈不会被清理 - 在第一次迭代中设置的所有 ThreadStatic 变量都保留在第二次迭代中(尽管我们过度 -写他们)
- 只有当 Parallel.ForEach 完成时,线程才会返回到线程池
因此我的问题是:这些属性何时从内存中删除?它们是在线程返回到 ThreadPool 时(大概是在 Parallel.ForEach 完成时)清理的,还是在稍后将线程分配给不同的 AppDomain 时清理的?我问是因为其中一些属性可能会消耗大量内存,我想确保它们占用内存的时间不会超过他们需要的时间。我不太喜欢在每次循环迭代结束时将它们显式设置为 null 的想法...
ThreadStatic
是线程专有存储space.
lifetime of ThreadStatic properties
ThreadStatic
是线程专有存储space,所以ThreadStatic
属性对象的生命周期就是线程的生命周期。
when we create a Parallel.ForEach loop, it gets assigned a handful of threads from the Thread Pool
Parallel
比这更复杂。它可以在执行期间根据需要调整它使用的线程数。线程可以同时 "enter" 和 "leave" 并行循环的 "ownership" 运行。
When one of these threads has completed a loop, and then starts a new loop, it does not have its stack cleaned - all ThreadStatic variables that were set in the first iteration remain for the second iteration
是的。它与 "stack" 没有任何关系。 ThreadStatic
是线程专有存储space,所以还是和线程有关
when are these properties removed from memory?
ThreadStatic
是一个线程专有存储space,所以当线程完成时它们会被清理。
Are they cleaned up when the thread is returned to the ThreadPool (presumably when the Parallel.ForEach completes)?
没有。线程仍然存在,所以 ThreadStatic
属性 对象也仍然存在。
I don't particularly like the idea of explicitly setting them to null at the end of each loop iteration
Peter Duniho 说得对:"I don't see anything in the little bit of code you posted that justifies the use of any of [ThreadStatic]
, ThreadLocal<T>
or AsyncLocal<T>
. For transient threads, typically the right approach is to just pass an appropriate context object to the thread... All of these other mechanisms are more heavy-weight and have semantics that don't seem to be needed here."
我想了解在 .NET 中使用并行处理时 ThreadStatic 数据何时被清除。
考虑以下(大幅缩减)代码:
我的背景class
public class AppContext
{
[ThreadStatic]
private static Person _person;
public Person Shopper
{
get => AppContext._person;
set => AppContext._person = value;
}
}
- 这个 class 会有很多属性 - 当然超过 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(...) 不是一个简单的函数,而是一个复杂的、多步骤的、同步的过程,几乎是一个 "application within an application"。因为它是同步的,所以我们可以使用 ThreadStatic 属性来保存特定于正在处理的项目的数据。
当线程(例如managedThreadId = 24)第一次进入并行循环时,那么singleContext.Shopper初始为null。
当该线程 (managedThreadId = 24) 初始化购物者时,该购物者被保存在 ThreadStatic 字段中,因此其他线程无法访问。
下一次同一个线程(managedThreadId = 24)重新进入循环(处理不同的项目),那么singleContext.Shopper仍然是在上一个循环。
所以,我的理解(如有错误请指正)是:
- 当我们创建一个 Parallel.ForEach 循环时,它会从线程池 中分配一些线程
- 当其中一个线程完成一个循环,然后开始一个新循环时,它的堆栈不会被清理 - 在第一次迭代中设置的所有 ThreadStatic 变量都保留在第二次迭代中(尽管我们过度 -写他们)
- 只有当 Parallel.ForEach 完成时,线程才会返回到线程池
因此我的问题是:这些属性何时从内存中删除?它们是在线程返回到 ThreadPool 时(大概是在 Parallel.ForEach 完成时)清理的,还是在稍后将线程分配给不同的 AppDomain 时清理的?我问是因为其中一些属性可能会消耗大量内存,我想确保它们占用内存的时间不会超过他们需要的时间。我不太喜欢在每次循环迭代结束时将它们显式设置为 null 的想法...
ThreadStatic
是线程专有存储space.
lifetime of ThreadStatic properties
ThreadStatic
是线程专有存储space,所以ThreadStatic
属性对象的生命周期就是线程的生命周期。
when we create a Parallel.ForEach loop, it gets assigned a handful of threads from the Thread Pool
Parallel
比这更复杂。它可以在执行期间根据需要调整它使用的线程数。线程可以同时 "enter" 和 "leave" 并行循环的 "ownership" 运行。
When one of these threads has completed a loop, and then starts a new loop, it does not have its stack cleaned - all ThreadStatic variables that were set in the first iteration remain for the second iteration
是的。它与 "stack" 没有任何关系。 ThreadStatic
是线程专有存储space,所以还是和线程有关
when are these properties removed from memory?
ThreadStatic
是一个线程专有存储space,所以当线程完成时它们会被清理。
Are they cleaned up when the thread is returned to the ThreadPool (presumably when the Parallel.ForEach completes)?
没有。线程仍然存在,所以 ThreadStatic
属性 对象也仍然存在。
I don't particularly like the idea of explicitly setting them to null at the end of each loop iteration
Peter Duniho 说得对:"I don't see anything in the little bit of code you posted that justifies the use of any of [ThreadStatic]
, ThreadLocal<T>
or AsyncLocal<T>
. For transient threads, typically the right approach is to just pass an appropriate context object to the thread... All of these other mechanisms are more heavy-weight and have semantics that don't seem to be needed here."