diff options
Diffstat (limited to 'src/mscorlib/src/System/Threading/ThreadLocal.cs')
-rw-r--r-- | src/mscorlib/src/System/Threading/ThreadLocal.cs | 815 |
1 files changed, 815 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/Threading/ThreadLocal.cs b/src/mscorlib/src/System/Threading/ThreadLocal.cs new file mode 100644 index 0000000000..b4cf12ab7c --- /dev/null +++ b/src/mscorlib/src/System/Threading/ThreadLocal.cs @@ -0,0 +1,815 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +#pragma warning disable 0420 + +// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +// +// +// +// A class that provides a simple, lightweight implementation of thread-local lazy-initialization, where a value is initialized once per accessing +// thread; this provides an alternative to using a ThreadStatic static variable and having +// to check the variable prior to every access to see if it's been initialized. +// +// +// +// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + +using System.Diagnostics; +using System.Collections.Generic; +using System.Security.Permissions; +using System.Diagnostics.Contracts; + +namespace System.Threading +{ + /// <summary> + /// Provides thread-local storage of data. + /// </summary> + /// <typeparam name="T">Specifies the type of data stored per-thread.</typeparam> + /// <remarks> + /// <para> + /// With the exception of <see cref="Dispose()"/>, all public and protected members of + /// <see cref="ThreadLocal{T}"/> are thread-safe and may be used + /// concurrently from multiple threads. + /// </para> + /// </remarks> + [DebuggerTypeProxy(typeof(SystemThreading_ThreadLocalDebugView<>))] + [DebuggerDisplay("IsValueCreated={IsValueCreated}, Value={ValueForDebugDisplay}, Count={ValuesCountForDebugDisplay}")] + [HostProtection(Synchronization = true, ExternalThreading = true)] + public class ThreadLocal<T> : IDisposable + { + + // a delegate that returns the created value, if null the created value will be default(T) + private Func<T> m_valueFactory; + + // + // ts_slotArray is a table of thread-local values for all ThreadLocal<T> instances + // + // So, when a thread reads ts_slotArray, it gets back an array of *all* ThreadLocal<T> values for this thread and this T. + // The slot relevant to this particular ThreadLocal<T> instance is determined by the m_idComplement instance field stored in + // the ThreadLocal<T> instance. + // + [ThreadStatic] + static LinkedSlotVolatile[] ts_slotArray; + + [ThreadStatic] + static FinalizationHelper ts_finalizationHelper; + + // Slot ID of this ThreadLocal<> instance. We store a bitwise complement of the ID (that is ~ID), which allows us to distinguish + // between the case when ID is 0 and an incompletely initialized object, either due to a thread abort in the constructor, or + // possibly due to a memory model issue in user code. + private int m_idComplement; + + // This field is set to true when the constructor completes. That is helpful for recognizing whether a constructor + // threw an exception - either due to invalid argument or due to a thread abort. Finally, the field is set to false + // when the instance is disposed. + private volatile bool m_initialized; + + // IdManager assigns and reuses slot IDs. Additionally, the object is also used as a global lock. + private static IdManager s_idManager = new IdManager(); + + // A linked list of all values associated with this ThreadLocal<T> instance. + // We create a dummy head node. That allows us to remove any (non-dummy) node without having to locate the m_linkedSlot field. + private LinkedSlot m_linkedSlot = new LinkedSlot(null); + + // Whether the Values property is supported + private bool m_trackAllValues; + + /// <summary> + /// Initializes the <see cref="System.Threading.ThreadLocal{T}"/> instance. + /// </summary> + public ThreadLocal() + { + Initialize(null, false); + } + + /// <summary> + /// Initializes the <see cref="System.Threading.ThreadLocal{T}"/> instance. + /// </summary> + /// <param name="trackAllValues">Whether to track all values set on the instance and expose them through the Values property.</param> + public ThreadLocal(bool trackAllValues) + { + Initialize(null, trackAllValues); + } + + + /// <summary> + /// Initializes the <see cref="System.Threading.ThreadLocal{T}"/> instance with the + /// specified <paramref name="valueFactory"/> function. + /// </summary> + /// <param name="valueFactory"> + /// The <see cref="T:System.Func{T}"/> invoked to produce a lazily-initialized value when + /// an attempt is made to retrieve <see cref="Value"/> without it having been previously initialized. + /// </param> + /// <exception cref="T:System.ArgumentNullException"> + /// <paramref name="valueFactory"/> is a null reference (Nothing in Visual Basic). + /// </exception> + public ThreadLocal(Func<T> valueFactory) + { + if (valueFactory == null) + throw new ArgumentNullException("valueFactory"); + + Initialize(valueFactory, false); + } + + /// <summary> + /// Initializes the <see cref="System.Threading.ThreadLocal{T}"/> instance with the + /// specified <paramref name="valueFactory"/> function. + /// </summary> + /// <param name="valueFactory"> + /// The <see cref="T:System.Func{T}"/> invoked to produce a lazily-initialized value when + /// an attempt is made to retrieve <see cref="Value"/> without it having been previously initialized. + /// </param> + /// <param name="trackAllValues">Whether to track all values set on the instance and expose them via the Values property.</param> + /// <exception cref="T:System.ArgumentNullException"> + /// <paramref name="valueFactory"/> is a null reference (Nothing in Visual Basic). + /// </exception> + public ThreadLocal(Func<T> valueFactory, bool trackAllValues) + { + if (valueFactory == null) + throw new ArgumentNullException("valueFactory"); + + Initialize(valueFactory, trackAllValues); + } + + private void Initialize(Func<T> valueFactory, bool trackAllValues) + { + m_valueFactory = valueFactory; + m_trackAllValues = trackAllValues; + + // Assign the ID and mark the instance as initialized. To avoid leaking IDs, we assign the ID and set m_initialized + // in a finally block, to avoid a thread abort in between the two statements. + try { } + finally + { + m_idComplement = ~s_idManager.GetId(); + + // As the last step, mark the instance as fully initialized. (Otherwise, if m_initialized=false, we know that an exception + // occurred in the constructor.) + m_initialized = true; + } + } + + /// <summary> + /// Releases the resources used by this <see cref="T:System.Threading.ThreadLocal{T}" /> instance. + /// </summary> + ~ThreadLocal() + { + // finalizer to return the type combination index to the pool + Dispose(false); + } + + #region IDisposable Members + + /// <summary> + /// Releases the resources used by this <see cref="T:System.Threading.ThreadLocal{T}" /> instance. + /// </summary> + /// <remarks> + /// Unlike most of the members of <see cref="T:System.Threading.ThreadLocal{T}"/>, this method is not thread-safe. + /// </remarks> + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// <summary> + /// Releases the resources used by this <see cref="T:System.Threading.ThreadLocal{T}" /> instance. + /// </summary> + /// <param name="disposing"> + /// A Boolean value that indicates whether this method is being called due to a call to <see cref="Dispose()"/>. + /// </param> + /// <remarks> + /// Unlike most of the members of <see cref="T:System.Threading.ThreadLocal{T}"/>, this method is not thread-safe. + /// </remarks> + protected virtual void Dispose(bool disposing) + { + int id; + + lock (s_idManager) + { + id = ~m_idComplement; + m_idComplement = 0; + + if (id < 0 || !m_initialized) + { + Contract.Assert(id >= 0 || !m_initialized, "expected id >= 0 if initialized"); + + // Handle double Dispose calls or disposal of an instance whose constructor threw an exception. + return; + } + m_initialized = false; + + for (LinkedSlot linkedSlot = m_linkedSlot.Next; linkedSlot != null; linkedSlot = linkedSlot.Next) + { + LinkedSlotVolatile[] slotArray = linkedSlot.SlotArray; + + if (slotArray == null) + { + // The thread that owns this slotArray has already finished. + continue; + } + + // Remove the reference from the LinkedSlot to the slot table. + linkedSlot.SlotArray = null; + + // And clear the references from the slot table to the linked slot and the value so that + // both can get garbage collected. + slotArray[id].Value.Value = default(T); + slotArray[id].Value = null; + } + } + m_linkedSlot = null; + s_idManager.ReturnId(id); + } + + #endregion + + /// <summary>Creates and returns a string representation of this instance for the current thread.</summary> + /// <returns>The result of calling <see cref="System.Object.ToString"/> on the <see cref="Value"/>.</returns> + /// <exception cref="T:System.NullReferenceException"> + /// The <see cref="Value"/> for the current thread is a null reference (Nothing in Visual Basic). + /// </exception> + /// <exception cref="T:System.InvalidOperationException"> + /// The initialization function referenced <see cref="Value"/> in an improper manner. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException"> + /// The <see cref="ThreadLocal{T}"/> instance has been disposed. + /// </exception> + /// <remarks> + /// Calling this method forces initialization for the current thread, as is the + /// case with accessing <see cref="Value"/> directly. + /// </remarks> + public override string ToString() + { + return Value.ToString(); + } + + /// <summary> + /// Gets or sets the value of this instance for the current thread. + /// </summary> + /// <exception cref="T:System.InvalidOperationException"> + /// The initialization function referenced <see cref="Value"/> in an improper manner. + /// </exception> + /// <exception cref="T:System.ObjectDisposedException"> + /// The <see cref="ThreadLocal{T}"/> instance has been disposed. + /// </exception> + /// <remarks> + /// If this instance was not previously initialized for the current thread, + /// accessing <see cref="Value"/> will attempt to initialize it. If an initialization function was + /// supplied during the construction, that initialization will happen by invoking the function + /// to retrieve the initial value for <see cref="Value"/>. Otherwise, the default value of + /// <typeparamref name="T"/> will be used. + /// </remarks> + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + public T Value + { + get + { + LinkedSlotVolatile[] slotArray = ts_slotArray; + LinkedSlot slot; + int id = ~m_idComplement; + + // + // Attempt to get the value using the fast path + // + if (slotArray != null // Has the slot array been initialized? + && id >= 0 // Is the ID non-negative (i.e., instance is not disposed)? + && id < slotArray.Length // Is the table large enough? + && (slot = slotArray[id].Value) != null // Has a LinkedSlot object has been allocated for this ID? + && m_initialized // Has the instance *still* not been disposed (important for a race condition with Dispose)? + ) + { + // We verified that the instance has not been disposed *after* we got a reference to the slot. + // This guarantees that we have a reference to the right slot. + // + // Volatile read of the LinkedSlotVolatile.Value property ensures that the m_initialized read + // will not be reordered before the read of slotArray[id]. + return slot.Value; + } + + return GetValueSlow(); + } + set + { + LinkedSlotVolatile[] slotArray = ts_slotArray; + LinkedSlot slot; + int id = ~m_idComplement; + + // + // Attempt to set the value using the fast path + // + if (slotArray != null // Has the slot array been initialized? + && id >= 0 // Is the ID non-negative (i.e., instance is not disposed)? + && id < slotArray.Length // Is the table large enough? + && (slot = slotArray[id].Value) != null // Has a LinkedSlot object has been allocated for this ID? + && m_initialized // Has the instance *still* not been disposed (important for a race condition with Dispose)? + ) + { + // We verified that the instance has not been disposed *after* we got a reference to the slot. + // This guarantees that we have a reference to the right slot. + // + // Volatile read of the LinkedSlotVolatile.Value property ensures that the m_initialized read + // will not be reordered before the read of slotArray[id]. + slot.Value = value; + } + else + { + SetValueSlow(value, slotArray); + } + } + } + + private T GetValueSlow() + { + // If the object has been disposed, the id will be -1. + int id = ~m_idComplement; + if (id < 0) + { + throw new ObjectDisposedException(Environment.GetResourceString("ThreadLocal_Disposed")); + } + + Debugger.NotifyOfCrossThreadDependency(); + + // Determine the initial value + T value; + if (m_valueFactory == null) + { + value = default(T); + } + else + { + value = m_valueFactory(); + + if (IsValueCreated) + { + throw new InvalidOperationException(Environment.GetResourceString("ThreadLocal_Value_RecursiveCallsToValue")); + } + } + + // Since the value has been previously uninitialized, we also need to set it (according to the ThreadLocal semantics). + Value = value; + return value; + } + + private void SetValueSlow(T value, LinkedSlotVolatile[] slotArray) + { + int id = ~m_idComplement; + + // If the object has been disposed, id will be -1. + if (id < 0) + { + throw new ObjectDisposedException(Environment.GetResourceString("ThreadLocal_Disposed")); + } + + // If a slot array has not been created on this thread yet, create it. + if (slotArray == null) + { + slotArray = new LinkedSlotVolatile[GetNewTableSize(id + 1)]; + ts_finalizationHelper = new FinalizationHelper(slotArray, m_trackAllValues); + ts_slotArray = slotArray; + } + + // If the slot array is not big enough to hold this ID, increase the table size. + if (id >= slotArray.Length) + { + GrowTable(ref slotArray, id + 1); + ts_finalizationHelper.SlotArray = slotArray; + ts_slotArray = slotArray; + } + + // If we are using the slot in this table for the first time, create a new LinkedSlot and add it into + // the linked list for this ThreadLocal instance. + if (slotArray[id].Value == null) + { + CreateLinkedSlot(slotArray, id, value); + } + else + { + // Volatile read of the LinkedSlotVolatile.Value property ensures that the m_initialized read + // that follows will not be reordered before the read of slotArray[id]. + LinkedSlot slot = slotArray[id].Value; + + // It is important to verify that the ThreadLocal instance has not been disposed. The check must come + // after capturing slotArray[id], but before assigning the value into the slot. This ensures that + // if this ThreadLocal instance was disposed on another thread and another ThreadLocal instance was + // created, we definitely won't assign the value into the wrong instance. + + if (!m_initialized) + { + throw new ObjectDisposedException(Environment.GetResourceString("ThreadLocal_Disposed")); + } + + slot.Value = value; + } + } + + /// <summary> + /// Creates a LinkedSlot and inserts it into the linked list for this ThreadLocal instance. + /// </summary> + private void CreateLinkedSlot(LinkedSlotVolatile[] slotArray, int id, T value) + { + // Create a LinkedSlot + var linkedSlot = new LinkedSlot(slotArray); + + // Insert the LinkedSlot into the linked list maintained by this ThreadLocal<> instance and into the slot array + lock (s_idManager) + { + // Check that the instance has not been disposed. It is important to check this under a lock, since + // Dispose also executes under a lock. + if (!m_initialized) + { + throw new ObjectDisposedException(Environment.GetResourceString("ThreadLocal_Disposed")); + } + + LinkedSlot firstRealNode = m_linkedSlot.Next; + + // + // Insert linkedSlot between nodes m_linkedSlot and firstRealNode. + // (m_linkedSlot is the dummy head node that should always be in the front.) + // + linkedSlot.Next = firstRealNode; + linkedSlot.Previous = m_linkedSlot; + linkedSlot.Value = value; + + if (firstRealNode != null) + { + firstRealNode.Previous = linkedSlot; + } + m_linkedSlot.Next = linkedSlot; + + // Assigning the slot under a lock prevents a race condition with Dispose (dispose also acquires the lock). + // Otherwise, it would be possible that the ThreadLocal instance is disposed, another one gets created + // with the same ID, and the write would go to the wrong instance. + slotArray[id].Value = linkedSlot; + } + } + + /// <summary> + /// Gets a list for all of the values currently stored by all of the threads that have accessed this instance. + /// </summary> + /// <exception cref="T:System.ObjectDisposedException"> + /// The <see cref="ThreadLocal{T}"/> instance has been disposed. + /// </exception> + public IList<T> Values + { + get + { + if (!m_trackAllValues) + { + throw new InvalidOperationException(Environment.GetResourceString("ThreadLocal_ValuesNotAvailable")); + } + + var list = GetValuesAsList(); // returns null if disposed + if (list == null) throw new ObjectDisposedException(Environment.GetResourceString("ThreadLocal_Disposed")); + return list; + } + } + + /// <summary>Gets all of the threads' values in a list.</summary> + private List<T> GetValuesAsList() + { + List<T> valueList = new List<T>(); + int id = ~m_idComplement; + if (id == -1) + { + return null; + } + + // Walk over the linked list of slots and gather the values associated with this ThreadLocal instance. + for (LinkedSlot linkedSlot = m_linkedSlot.Next; linkedSlot != null; linkedSlot = linkedSlot.Next) + { + // We can safely read linkedSlot.Value. Even if this ThreadLocal has been disposed in the meantime, the LinkedSlot + // objects will never be assigned to another ThreadLocal instance. + valueList.Add(linkedSlot.Value); + } + + return valueList; + } + + /// <summary>Gets the number of threads that have data in this instance.</summary> + private int ValuesCountForDebugDisplay + { + get + { + int count = 0; + for (LinkedSlot linkedSlot = m_linkedSlot.Next; linkedSlot != null; linkedSlot = linkedSlot.Next) + { + count++; + } + return count; + } + } + + /// <summary> + /// Gets whether <see cref="Value"/> is initialized on the current thread. + /// </summary> + /// <exception cref="T:System.ObjectDisposedException"> + /// The <see cref="ThreadLocal{T}"/> instance has been disposed. + /// </exception> + public bool IsValueCreated + { + get + { + int id = ~m_idComplement; + if (id < 0) + { + throw new ObjectDisposedException(Environment.GetResourceString("ThreadLocal_Disposed")); + } + + LinkedSlotVolatile[] slotArray = ts_slotArray; + return slotArray != null && id < slotArray.Length && slotArray[id].Value != null; + } + } + + + /// <summary>Gets the value of the ThreadLocal<T> for debugging display purposes. It takes care of getting + /// the value for the current thread in the ThreadLocal mode.</summary> + internal T ValueForDebugDisplay + { + get + { + LinkedSlotVolatile[] slotArray = ts_slotArray; + int id = ~m_idComplement; + + LinkedSlot slot; + if (slotArray == null || id >= slotArray.Length || (slot = slotArray[id].Value) == null || !m_initialized) + return default(T); + return slot.Value; + } + } + + /// <summary>Gets the values of all threads that accessed the ThreadLocal<T>.</summary> + internal List<T> ValuesForDebugDisplay // same as Values property, but doesn't throw if disposed + { + get { return GetValuesAsList(); } + } + + /// <summary> + /// Resizes a table to a certain length (or larger). + /// </summary> + private void GrowTable(ref LinkedSlotVolatile[] table, int minLength) + { + Contract.Assert(table.Length < minLength); + + // Determine the size of the new table and allocate it. + int newLen = GetNewTableSize(minLength); + LinkedSlotVolatile[] newTable = new LinkedSlotVolatile[newLen]; + + // + // The lock is necessary to avoid a race with ThreadLocal.Dispose. GrowTable has to point all + // LinkedSlot instances referenced in the old table to reference the new table. Without locking, + // Dispose could use a stale SlotArray reference and clear out a slot in the old array only, while + // the value continues to be referenced from the new (larger) array. + // + lock (s_idManager) + { + for (int i = 0; i < table.Length; i++) + { + LinkedSlot linkedSlot = table[i].Value; + if (linkedSlot != null && linkedSlot.SlotArray != null) + { + linkedSlot.SlotArray = newTable; + newTable[i] = table[i]; + } + } + } + + table = newTable; + } + + /// <summary> + /// Chooses the next larger table size + /// </summary> + private static int GetNewTableSize(int minSize) + { + if ((uint)minSize > Array.MaxArrayLength) + { + // Intentionally return a value that will result in an OutOfMemoryException + return int.MaxValue; + } + Contract.Assert(minSize > 0); + + // + // Round up the size to the next power of 2 + // + // The algorithm takes three steps: + // input -> subtract one -> propagate 1-bits to the right -> add one + // + // Let's take a look at the 3 steps in both interesting cases: where the input + // is (Example 1) and isn't (Example 2) a power of 2. + // + // Example 1: 100000 -> 011111 -> 011111 -> 100000 + // Example 2: 011010 -> 011001 -> 011111 -> 100000 + // + int newSize = minSize; + + // Step 1: Decrement + newSize--; + + // Step 2: Propagate 1-bits to the right. + newSize |= newSize >> 1; + newSize |= newSize >> 2; + newSize |= newSize >> 4; + newSize |= newSize >> 8; + newSize |= newSize >> 16; + + // Step 3: Increment + newSize++; + + // Don't set newSize to more than Array.MaxArrayLength + if ((uint)newSize > Array.MaxArrayLength) + { + newSize = Array.MaxArrayLength; + } + + return newSize; + } + + /// <summary> + /// A wrapper struct used as LinkedSlotVolatile[] - an array of LinkedSlot instances, but with volatile semantics + /// on array accesses. + /// </summary> + private struct LinkedSlotVolatile + { + internal volatile LinkedSlot Value; + } + + /// <summary> + /// A node in the doubly-linked list stored in the ThreadLocal instance. + /// + /// The value is stored in one of two places: + /// + /// 1. If SlotArray is not null, the value is in SlotArray.Table[id] + /// 2. If SlotArray is null, the value is in FinalValue. + /// </summary> + private sealed class LinkedSlot + { + internal LinkedSlot(LinkedSlotVolatile[] slotArray) + { + SlotArray = slotArray; + } + + // The next LinkedSlot for this ThreadLocal<> instance + internal volatile LinkedSlot Next; + + // The previous LinkedSlot for this ThreadLocal<> instance + internal volatile LinkedSlot Previous; + + // The SlotArray that stores this LinkedSlot at SlotArray.Table[id]. + internal volatile LinkedSlotVolatile[] SlotArray; + + // The value for this slot. + internal T Value; + } + + /// <summary> + /// A manager class that assigns IDs to ThreadLocal instances + /// </summary> + private class IdManager + { + // The next ID to try + private int m_nextIdToTry = 0; + + // Stores whether each ID is free or not. Additionally, the object is also used as a lock for the IdManager. + private List<bool> m_freeIds = new List<bool>(); + + internal int GetId() + { + lock (m_freeIds) + { + int availableId = m_nextIdToTry; + while (availableId < m_freeIds.Count) + { + if (m_freeIds[availableId]) { break; } + availableId++; + } + + if (availableId == m_freeIds.Count) + { + m_freeIds.Add(false); + } + else + { + m_freeIds[availableId] = false; + } + + m_nextIdToTry = availableId + 1; + + return availableId; + } + } + + // Return an ID to the pool + internal void ReturnId(int id) + { + lock (m_freeIds) + { + m_freeIds[id] = true; + if (id < m_nextIdToTry) m_nextIdToTry = id; + } + } + } + + /// <summary> + /// A class that facilitates ThreadLocal cleanup after a thread exits. + /// + /// After a thread with an associated thread-local table has exited, the FinalizationHelper + /// is reponsible for removing back-references to the table. Since an instance of FinalizationHelper + /// is only referenced from a single thread-local slot, the FinalizationHelper will be GC'd once + /// the thread has exited. + /// + /// The FinalizationHelper then locates all LinkedSlot instances with back-references to the table + /// (all those LinkedSlot instances can be found by following references from the table slots) and + /// releases the table so that it can get GC'd. + /// </summary> + private class FinalizationHelper + { + internal LinkedSlotVolatile[] SlotArray; + private bool m_trackAllValues; + + internal FinalizationHelper(LinkedSlotVolatile[] slotArray, bool trackAllValues) + { + SlotArray = slotArray; + m_trackAllValues = trackAllValues; + } + + ~FinalizationHelper() + { + LinkedSlotVolatile[] slotArray = SlotArray; + Contract.Assert(slotArray != null); + + for (int i = 0; i < slotArray.Length; i++) + { + LinkedSlot linkedSlot = slotArray[i].Value; + if (linkedSlot == null) + { + // This slot in the table is empty + continue; + } + + if (m_trackAllValues) + { + // Set the SlotArray field to null to release the slot array. + linkedSlot.SlotArray = null; + } + else + { + // Remove the LinkedSlot from the linked list. Once the FinalizationHelper is done, all back-references to + // the table will be have been removed, and so the table can get GC'd. + lock (s_idManager) + { + if (linkedSlot.Next != null) + { + linkedSlot.Next.Previous = linkedSlot.Previous; + } + + // Since the list uses a dummy head node, the Previous reference should never be null. + Contract.Assert(linkedSlot.Previous != null); + linkedSlot.Previous.Next = linkedSlot.Next; + } + } + } + } + } + } + + /// <summary>A debugger view of the ThreadLocal<T> to surface additional debugging properties and + /// to ensure that the ThreadLocal<T> does not become initialized if it was not already.</summary> + internal sealed class SystemThreading_ThreadLocalDebugView<T> + { + //The ThreadLocal object being viewed. + private readonly ThreadLocal<T> m_tlocal; + + /// <summary>Constructs a new debugger view object for the provided ThreadLocal object.</summary> + /// <param name="tlocal">A ThreadLocal object to browse in the debugger.</param> + public SystemThreading_ThreadLocalDebugView(ThreadLocal<T> tlocal) + { + m_tlocal = tlocal; + } + + /// <summary>Returns whether the ThreadLocal object is initialized or not.</summary> + public bool IsValueCreated + { + get { return m_tlocal.IsValueCreated; } + } + + /// <summary>Returns the value of the ThreadLocal object.</summary> + public T Value + { + get + { + return m_tlocal.ValueForDebugDisplay; + } + } + + /// <summary>Return all values for all threads that have accessed this instance.</summary> + public List<T> Values + { + get + { + return m_tlocal.ValuesForDebugDisplay; + } + } + } +} |