1 #pragma warning disable 0420
4 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
11 // <OWNER>Microsoft</OWNER>
13 // A class that provides a simple, lightweight implementation of thread-local lazy-initialization, where a value is initialized once per accessing
14 // thread; this provides an alternative to using a ThreadStatic static variable and having
15 // to check the variable prior to every access to see if it's been initialized.
19 // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
21 using System.Diagnostics;
22 using System.Collections.Generic;
23 using System.Security.Permissions;
24 using System.Diagnostics.Contracts;
26 namespace System.Threading
29 /// Provides thread-local storage of data.
31 /// <typeparam name="T">Specifies the type of data stored per-thread.</typeparam>
34 /// With the exception of <see cref="Dispose()"/>, all public and protected members of
35 /// <see cref="ThreadLocal{T}"/> are thread-safe and may be used
36 /// concurrently from multiple threads.
39 [DebuggerTypeProxy(typeof(SystemThreading_ThreadLocalDebugView<>))]
40 [DebuggerDisplay("IsValueCreated={IsValueCreated}, Value={ValueForDebugDisplay}, Count={ValuesCountForDebugDisplay}")]
41 [HostProtection(Synchronization = true, ExternalThreading = true)]
42 public class ThreadLocal<T> : IDisposable
45 // a delegate that returns the created value, if null the created value will be default(T)
46 private Func<T> m_valueFactory;
49 // ts_slotArray is a table of thread-local values for all ThreadLocal<T> instances
51 // So, when a thread reads ts_slotArray, it gets back an array of *all* ThreadLocal<T> values for this thread and this T.
52 // The slot relevant to this particular ThreadLocal<T> instance is determined by the m_idComplement instance field stored in
53 // the ThreadLocal<T> instance.
56 static LinkedSlotVolatile[] ts_slotArray;
59 static FinalizationHelper ts_finalizationHelper;
61 // Slot ID of this ThreadLocal<> instance. We store a bitwise complement of the ID (that is ~ID), which allows us to distinguish
62 // between the case when ID is 0 and an incompletely initialized object, either due to a thread abort in the constructor, or
63 // possibly due to a memory model issue in user code.
64 private int m_idComplement;
66 // This field is set to true when the constructor completes. That is helpful for recognizing whether a constructor
67 // threw an exception - either due to invalid argument or due to a thread abort. Finally, the field is set to false
68 // when the instance is disposed.
69 private volatile bool m_initialized;
71 // IdManager assigns and reuses slot IDs. Additionally, the object is also used as a global lock.
72 private static IdManager s_idManager = new IdManager();
74 // A linked list of all values associated with this ThreadLocal<T> instance.
75 // We create a dummy head node. That allows us to remove any (non-dummy) node without having to locate the m_linkedSlot field.
76 private LinkedSlot m_linkedSlot = new LinkedSlot(null);
78 // Whether the Values property is supported
79 private bool m_trackAllValues;
82 /// Initializes the <see cref="System.Threading.ThreadLocal{T}"/> instance.
86 Initialize(null, false);
90 /// Initializes the <see cref="System.Threading.ThreadLocal{T}"/> instance.
92 /// <param name="trackAllValues">Whether to track all values set on the instance and expose them through the Values property.</param>
93 public ThreadLocal(bool trackAllValues)
95 Initialize(null, trackAllValues);
100 /// Initializes the <see cref="System.Threading.ThreadLocal{T}"/> instance with the
101 /// specified <paramref name="valueFactory"/> function.
103 /// <param name="valueFactory">
104 /// The <see cref="T:System.Func{T}"/> invoked to produce a lazily-initialized value when
105 /// an attempt is made to retrieve <see cref="Value"/> without it having been previously initialized.
107 /// <exception cref="T:System.ArgumentNullException">
108 /// <paramref name="valueFactory"/> is a null reference (Nothing in Visual Basic).
110 public ThreadLocal(Func<T> valueFactory)
112 if (valueFactory == null)
113 throw new ArgumentNullException("valueFactory");
115 Initialize(valueFactory, false);
119 /// Initializes the <see cref="System.Threading.ThreadLocal{T}"/> instance with the
120 /// specified <paramref name="valueFactory"/> function.
122 /// <param name="valueFactory">
123 /// The <see cref="T:System.Func{T}"/> invoked to produce a lazily-initialized value when
124 /// an attempt is made to retrieve <see cref="Value"/> without it having been previously initialized.
126 /// <param name="trackAllValues">Whether to track all values set on the instance and expose them via the Values property.</param>
127 /// <exception cref="T:System.ArgumentNullException">
128 /// <paramref name="valueFactory"/> is a null reference (Nothing in Visual Basic).
130 public ThreadLocal(Func<T> valueFactory, bool trackAllValues)
132 if (valueFactory == null)
133 throw new ArgumentNullException("valueFactory");
135 Initialize(valueFactory, trackAllValues);
138 private void Initialize(Func<T> valueFactory, bool trackAllValues)
140 m_valueFactory = valueFactory;
141 m_trackAllValues = trackAllValues;
143 // Assign the ID and mark the instance as initialized. To avoid leaking IDs, we assign the ID and set m_initialized
144 // in a finally block, to avoid a thread abort in between the two statements.
148 m_idComplement = ~s_idManager.GetId();
150 // As the last step, mark the instance as fully initialized. (Otherwise, if m_initialized=false, we know that an exception
151 // occurred in the constructor.)
152 m_initialized = true;
157 /// Releases the resources used by this <see cref="T:System.Threading.ThreadLocal{T}" /> instance.
161 // finalizer to return the type combination index to the pool
165 #region IDisposable Members
168 /// Releases the resources used by this <see cref="T:System.Threading.ThreadLocal{T}" /> instance.
171 /// Unlike most of the members of <see cref="T:System.Threading.ThreadLocal{T}"/>, this method is not thread-safe.
173 public void Dispose()
176 GC.SuppressFinalize(this);
180 /// Releases the resources used by this <see cref="T:System.Threading.ThreadLocal{T}" /> instance.
182 /// <param name="disposing">
183 /// A Boolean value that indicates whether this method is being called due to a call to <see cref="Dispose()"/>.
186 /// Unlike most of the members of <see cref="T:System.Threading.ThreadLocal{T}"/>, this method is not thread-safe.
188 protected virtual void Dispose(bool disposing)
194 id = ~m_idComplement;
197 if (id < 0 || !m_initialized)
199 Contract.Assert(id >= 0 || !m_initialized, "expected id >= 0 if initialized");
201 // Handle double Dispose calls or disposal of an instance whose constructor threw an exception.
204 m_initialized = false;
206 for (LinkedSlot linkedSlot = m_linkedSlot.Next; linkedSlot != null; linkedSlot = linkedSlot.Next)
208 LinkedSlotVolatile[] slotArray = linkedSlot.SlotArray;
210 if (slotArray == null)
212 // The thread that owns this slotArray has already finished.
216 // Remove the reference from the LinkedSlot to the slot table.
217 linkedSlot.SlotArray = null;
219 // And clear the references from the slot table to the linked slot and the value so that
220 // both can get garbage collected.
221 slotArray[id].Value.Value = default(T);
222 slotArray[id].Value = null;
226 s_idManager.ReturnId(id);
231 /// <summary>Creates and returns a string representation of this instance for the current thread.</summary>
232 /// <returns>The result of calling <see cref="System.Object.ToString"/> on the <see cref="Value"/>.</returns>
233 /// <exception cref="T:System.NullReferenceException">
234 /// The <see cref="Value"/> for the current thread is a null reference (Nothing in Visual Basic).
236 /// <exception cref="T:System.InvalidOperationException">
237 /// The initialization function referenced <see cref="Value"/> in an improper manner.
239 /// <exception cref="T:System.ObjectDisposedException">
240 /// The <see cref="ThreadLocal{T}"/> instance has been disposed.
243 /// Calling this method forces initialization for the current thread, as is the
244 /// case with accessing <see cref="Value"/> directly.
246 public override string ToString()
248 return Value.ToString();
252 /// Gets or sets the value of this instance for the current thread.
254 /// <exception cref="T:System.InvalidOperationException">
255 /// The initialization function referenced <see cref="Value"/> in an improper manner.
257 /// <exception cref="T:System.ObjectDisposedException">
258 /// The <see cref="ThreadLocal{T}"/> instance has been disposed.
261 /// If this instance was not previously initialized for the current thread,
262 /// accessing <see cref="Value"/> will attempt to initialize it. If an initialization function was
263 /// supplied during the construction, that initialization will happen by invoking the function
264 /// to retrieve the initial value for <see cref="Value"/>. Otherwise, the default value of
265 /// <typeparamref name="T"/> will be used.
267 [DebuggerBrowsable(DebuggerBrowsableState.Never)]
272 LinkedSlotVolatile[] slotArray = ts_slotArray;
274 int id = ~m_idComplement;
277 // Attempt to get the value using the fast path
279 if (slotArray != null // Has the slot array been initialized?
280 && id >= 0 // Is the ID non-negative (i.e., instance is not disposed)?
281 && id < slotArray.Length // Is the table large enough?
282 && (slot = slotArray[id].Value) != null // Has a LinkedSlot object has been allocated for this ID?
283 && m_initialized // Has the instance *still* not been disposed (important for ----s with Dispose)?
286 // We verified that the instance has not been disposed *after* we got a reference to the slot.
287 // This guarantees that we have a reference to the right slot.
289 // Volatile read of the LinkedSlotVolatile.Value property ensures that the m_initialized read
290 // will not be reordered before the read of slotArray[id].
294 return GetValueSlow();
298 LinkedSlotVolatile[] slotArray = ts_slotArray;
300 int id = ~m_idComplement;
303 // Attempt to set the value using the fast path
305 if (slotArray != null // Has the slot array been initialized?
306 && id >= 0 // Is the ID non-negative (i.e., instance is not disposed)?
307 && id < slotArray.Length // Is the table large enough?
308 && (slot = slotArray[id].Value) != null // Has a LinkedSlot object has been allocated for this ID?
309 && m_initialized // Has the instance *still* not been disposed (important for ----s with Dispose)?
312 // We verified that the instance has not been disposed *after* we got a reference to the slot.
313 // This guarantees that we have a reference to the right slot.
315 // Volatile read of the LinkedSlotVolatile.Value property ensures that the m_initialized read
316 // will not be reordered before the read of slotArray[id].
321 SetValueSlow(value, slotArray);
326 private T GetValueSlow()
328 // If the object has been disposed, the id will be -1.
329 int id = ~m_idComplement;
332 throw new ObjectDisposedException(Environment.GetResourceString("ThreadLocal_Disposed"));
335 Debugger.NotifyOfCrossThreadDependency();
337 // Determine the initial value
339 if (m_valueFactory == null)
345 value = m_valueFactory();
349 throw new InvalidOperationException(Environment.GetResourceString("ThreadLocal_Value_RecursiveCallsToValue"));
353 // Since the value has been previously uninitialized, we also need to set it (according to the ThreadLocal semantics).
358 private void SetValueSlow(T value, LinkedSlotVolatile[] slotArray)
360 int id = ~m_idComplement;
362 // If the object has been disposed, id will be -1.
365 throw new ObjectDisposedException(Environment.GetResourceString("ThreadLocal_Disposed"));
368 // If a slot array has not been created on this thread yet, create it.
369 if (slotArray == null)
371 slotArray = new LinkedSlotVolatile[GetNewTableSize(id + 1)];
372 ts_finalizationHelper = new FinalizationHelper(slotArray, m_trackAllValues);
373 ts_slotArray = slotArray;
376 // If the slot array is not big enough to hold this ID, increase the table size.
377 if (id >= slotArray.Length)
379 GrowTable(ref slotArray, id + 1);
380 ts_finalizationHelper.SlotArray = slotArray;
381 ts_slotArray = slotArray;
384 // If we are using the slot in this table for the first time, create a new LinkedSlot and add it into
385 // the linked list for this ThreadLocal instance.
386 if (slotArray[id].Value == null)
388 CreateLinkedSlot(slotArray, id, value);
392 // Volatile read of the LinkedSlotVolatile.Value property ensures that the m_initialized read
393 // that follows will not be reordered before the read of slotArray[id].
394 LinkedSlot slot = slotArray[id].Value;
396 // It is important to verify that the ThreadLocal instance has not been disposed. The check must come
397 // after capturing slotArray[id], but before assigning the value into the slot. This ensures that
398 // if this ThreadLocal instance was disposed on another thread and another ThreadLocal instance was
399 // created, we definitely won't assign the value into the wrong instance.
403 throw new ObjectDisposedException(Environment.GetResourceString("ThreadLocal_Disposed"));
411 /// Creates a LinkedSlot and inserts it into the linked list for this ThreadLocal instance.
413 private void CreateLinkedSlot(LinkedSlotVolatile[] slotArray, int id, T value)
415 // Create a LinkedSlot
416 var linkedSlot = new LinkedSlot(slotArray);
418 // Insert the LinkedSlot into the linked list maintained by this ThreadLocal<> instance and into the slot array
421 // Check that the instance has not been disposed. It is important to check this under a lock, since
422 // Dispose also executes under a lock.
425 throw new ObjectDisposedException(Environment.GetResourceString("ThreadLocal_Disposed"));
428 LinkedSlot firstRealNode = m_linkedSlot.Next;
431 // Insert linkedSlot between nodes m_linkedSlot and firstRealNode.
432 // (m_linkedSlot is the dummy head node that should always be in the front.)
434 linkedSlot.Next = firstRealNode;
435 linkedSlot.Previous = m_linkedSlot;
436 linkedSlot.Value = value;
438 if (firstRealNode != null)
440 firstRealNode.Previous = linkedSlot;
442 m_linkedSlot.Next = linkedSlot;
444 // Assigning the slot under a lock prevents a ---- with Dispose (dispose also acquires the lock).
445 // Otherwise, it would be possible that the ThreadLocal instance is disposed, another one gets created
446 // with the same ID, and the write would go to the wrong instance.
447 slotArray[id].Value = linkedSlot;
452 /// Gets a list for all of the values currently stored by all of the threads that have accessed this instance.
454 /// <exception cref="T:System.ObjectDisposedException">
455 /// The <see cref="ThreadLocal{T}"/> instance has been disposed.
457 public IList<T> Values
461 if (!m_trackAllValues)
463 throw new InvalidOperationException(Environment.GetResourceString("ThreadLocal_ValuesNotAvailable"));
466 var list = GetValuesAsList(); // returns null if disposed
467 if (list == null) throw new ObjectDisposedException(Environment.GetResourceString("ThreadLocal_Disposed"));
472 /// <summary>Gets all of the threads' values in a list.</summary>
473 private List<T> GetValuesAsList()
475 List<T> valueList = new List<T>();
476 int id = ~m_idComplement;
482 // Walk over the linked list of slots and gather the values associated with this ThreadLocal instance.
483 for (LinkedSlot linkedSlot = m_linkedSlot.Next; linkedSlot != null; linkedSlot = linkedSlot.Next)
485 // We can safely read linkedSlot.Value. Even if this ThreadLocal has been disposed in the meantime, the LinkedSlot
486 // objects will never be assigned to another ThreadLocal instance.
487 valueList.Add(linkedSlot.Value);
493 /// <summary>Gets the number of threads that have data in this instance.</summary>
494 private int ValuesCountForDebugDisplay
499 for (LinkedSlot linkedSlot = m_linkedSlot.Next; linkedSlot != null; linkedSlot = linkedSlot.Next)
508 /// Gets whether <see cref="Value"/> is initialized on the current thread.
510 /// <exception cref="T:System.ObjectDisposedException">
511 /// The <see cref="ThreadLocal{T}"/> instance has been disposed.
513 public bool IsValueCreated
517 int id = ~m_idComplement;
520 throw new ObjectDisposedException(Environment.GetResourceString("ThreadLocal_Disposed"));
523 LinkedSlotVolatile[] slotArray = ts_slotArray;
524 return slotArray != null && id < slotArray.Length && slotArray[id].Value != null;
529 /// <summary>Gets the value of the ThreadLocal<T> for debugging display purposes. It takes care of getting
530 /// the value for the current thread in the ThreadLocal mode.</summary>
531 internal T ValueForDebugDisplay
535 LinkedSlotVolatile[] slotArray = ts_slotArray;
536 int id = ~m_idComplement;
539 if (slotArray == null || id >= slotArray.Length || (slot = slotArray[id].Value) == null || !m_initialized)
545 /// <summary>Gets the values of all threads that accessed the ThreadLocal<T>.</summary>
546 internal List<T> ValuesForDebugDisplay // same as Values property, but doesn't throw if disposed
548 get { return GetValuesAsList(); }
552 /// Resizes a table to a certain length (or larger).
554 private void GrowTable(ref LinkedSlotVolatile[] table, int minLength)
556 Contract.Assert(table.Length < minLength);
558 // Determine the size of the new table and allocate it.
559 int newLen = GetNewTableSize(minLength);
560 LinkedSlotVolatile[] newTable = new LinkedSlotVolatile[newLen];
563 // The lock is necessary to avoid a race with ThreadLocal.Dispose. GrowTable has to point all
564 // LinkedSlot instances referenced in the old table to reference the new table. Without locking,
565 // Dispose could use a stale SlotArray reference and clear out a slot in the old array only, while
566 // the value continues to be referenced from the new (larger) array.
570 for (int i = 0; i < table.Length; i++)
572 LinkedSlot linkedSlot = table[i].Value;
573 if (linkedSlot != null && linkedSlot.SlotArray != null)
575 linkedSlot.SlotArray = newTable;
576 newTable[i] = table[i];
585 /// Chooses the next larger table size
587 private static int GetNewTableSize(int minSize)
589 if ((uint)minSize > Array_ReferenceSources.MaxArrayLength)
591 // Intentionally return a value that will result in an OutOfMemoryException
594 Contract.Assert(minSize > 0);
597 // Round up the size to the next power of 2
599 // The algorithm takes three steps:
600 // input -> subtract one -> propagate 1-bits to the right -> add one
602 // Let's take a look at the 3 steps in both interesting cases: where the input
603 // is (Example 1) and isn't (Example 2) a power of 2.
605 // Example 1: 100000 -> 011111 -> 011111 -> 100000
606 // Example 2: 011010 -> 011001 -> 011111 -> 100000
608 int newSize = minSize;
613 // Step 2: Propagate 1-bits to the right.
614 newSize |= newSize >> 1;
615 newSize |= newSize >> 2;
616 newSize |= newSize >> 4;
617 newSize |= newSize >> 8;
618 newSize |= newSize >> 16;
623 // Don't set newSize to more than Array.MaxArrayLength
624 if ((uint)newSize > Array_ReferenceSources.MaxArrayLength)
626 newSize = Array_ReferenceSources.MaxArrayLength;
633 /// A wrapper struct used as LinkedSlotVolatile[] - an array of LinkedSlot instances, but with volatile semantics
634 /// on array accesses.
636 private struct LinkedSlotVolatile
638 internal volatile LinkedSlot Value;
642 /// A node in the doubly-linked list stored in the ThreadLocal instance.
644 /// The value is stored in one of two places:
646 /// 1. If SlotArray is not null, the value is in SlotArray.Table[id]
647 /// 2. If SlotArray is null, the value is in FinalValue.
649 private sealed class LinkedSlot
651 internal LinkedSlot(LinkedSlotVolatile[] slotArray)
653 SlotArray = slotArray;
656 // The next LinkedSlot for this ThreadLocal<> instance
657 internal volatile LinkedSlot Next;
659 // The previous LinkedSlot for this ThreadLocal<> instance
660 internal volatile LinkedSlot Previous;
662 // The SlotArray that stores this LinkedSlot at SlotArray.Table[id].
663 internal volatile LinkedSlotVolatile[] SlotArray;
665 // The value for this slot.
670 /// A manager class that assigns IDs to ThreadLocal instances
672 private class IdManager
674 // The next ID to try
675 private int m_nextIdToTry = 0;
677 // Stores whether each ID is free or not. Additionally, the object is also used as a lock for the IdManager.
678 private List<bool> m_freeIds = new List<bool>();
684 int availableId = m_nextIdToTry;
685 while (availableId < m_freeIds.Count)
687 if (m_freeIds[availableId]) { break; }
691 if (availableId == m_freeIds.Count)
693 m_freeIds.Add(false);
697 m_freeIds[availableId] = false;
700 m_nextIdToTry = availableId + 1;
706 // Return an ID to the pool
707 internal void ReturnId(int id)
711 m_freeIds[id] = true;
712 if (id < m_nextIdToTry) m_nextIdToTry = id;
718 /// A class that facilitates ThreadLocal cleanup after a thread exits.
720 /// After a thread with an associated thread-local table has exited, the FinalizationHelper
721 /// is reponsible for removing back-references to the table. Since an instance of FinalizationHelper
722 /// is only referenced from a single thread-local slot, the FinalizationHelper will be GC'd once
723 /// the thread has exited.
725 /// The FinalizationHelper then locates all LinkedSlot instances with back-references to the table
726 /// (all those LinkedSlot instances can be found by following references from the table slots) and
727 /// releases the table so that it can get GC'd.
729 private class FinalizationHelper
731 internal LinkedSlotVolatile[] SlotArray;
732 private bool m_trackAllValues;
734 internal FinalizationHelper(LinkedSlotVolatile[] slotArray, bool trackAllValues)
736 SlotArray = slotArray;
737 m_trackAllValues = trackAllValues;
740 ~FinalizationHelper()
742 LinkedSlotVolatile[] slotArray = SlotArray;
743 Contract.Assert(slotArray != null);
745 for (int i = 0; i < slotArray.Length; i++)
747 LinkedSlot linkedSlot = slotArray[i].Value;
748 if (linkedSlot == null)
750 // This slot in the table is empty
754 if (m_trackAllValues)
756 // Set the SlotArray field to null to release the slot array.
757 linkedSlot.SlotArray = null;
761 // Remove the LinkedSlot from the linked list. Once the FinalizationHelper is done, all back-references to
762 // the table will be have been removed, and so the table can get GC'd.
765 if (linkedSlot.Next != null)
767 linkedSlot.Next.Previous = linkedSlot.Previous;
770 // Since the list uses a dummy head node, the Previous reference should never be null.
771 Contract.Assert(linkedSlot.Previous != null);
772 linkedSlot.Previous.Next = linkedSlot.Next;
780 /// <summary>A debugger view of the ThreadLocal<T> to surface additional debugging properties and
781 /// to ensure that the ThreadLocal<T> does not become initialized if it was not already.</summary>
782 internal sealed class SystemThreading_ThreadLocalDebugView<T>
784 //The ThreadLocal object being viewed.
785 private readonly ThreadLocal<T> m_tlocal;
787 /// <summary>Constructs a new debugger view object for the provided ThreadLocal object.</summary>
788 /// <param name="tlocal">A ThreadLocal object to browse in the debugger.</param>
789 public SystemThreading_ThreadLocalDebugView(ThreadLocal<T> tlocal)
794 /// <summary>Returns whether the ThreadLocal object is initialized or not.</summary>
795 public bool IsValueCreated
797 get { return m_tlocal.IsValueCreated; }
800 /// <summary>Returns the value of the ThreadLocal object.</summary>
805 return m_tlocal.ValueForDebugDisplay;
809 /// <summary>Return all values for all threads that have accessed this instance.</summary>
810 public List<T> Values
814 return m_tlocal.ValuesForDebugDisplay;