Recipe 15.12 Storing Thread-Specific Data Privately
Problem
You want to store
thread-specific data discovered at runtime on a thread that will be
accessible only to code running within that thread.
Solution
Use the
AllocateDataSlot or
AllocateNamedDataSlot method on the
Thread class to reserve a thread local storage
(TLS) slot. Using TLS, a large structure can be stored in a data slot
on a thread and used in many different methods. This can be done
without having to pass the structure as a parameter.
For this example, a structure called Data here
represents a structure that can grow to be very large in size:
public struct Data
{
// Application data is stored here
}
Before using this structure, a data slot has to be created in TLS to
store the structure. The following code creates an instance of the
Data structure and stores it in the data slot
named AppDataSlot:
Data appData = new Data( );
Thread.SetData(Thread.GetNamedDataSlot("appDataSlot"), appData);
Whenever this structure is needed, it can be retrieved with a call to
Thread.GetData. The following line of code gets
the appData structure from the data slot named
appDataSlot:
Data storedAppData = (Data)Thread.GetData(Thread.GetNamedDataSlot("appDataSlot"));
At this point, the storedAppData structure can be
read or modified. After the action has been performed on the
storedAppData structure,
storedAppData must be placed back into the data
slot named appDataSlot:
Thread.SetData(Thread.GetNamedDataSlot("appDataSlot"), appData);
Once the application is finished using this structure, the data slot
can be released from memory using the following method call:
Thread.FreeNamedDataSlot("appDataSlot");
The following simple class shows how TLS can be used to store a
structure:
using System;
using System.Threading;
public class HandleStructure
{
public static void Main( )
{
// Create structure instance and store it in the named data slot
Data appData = new Data( );
Thread.SetData(Thread.GetNamedDataSlot("appDataSlot"), appData);
// Call another method that will use this structure
HandleStructure.MethodB( );
// When done, free this data slot
Thread.FreeNamedDataSlot("appDataSlot");
}
public static void MethodB( )
{
// Get the structure instance from the named data slot
Data storedAppData = (Data)Thread.GetData(
Thread.GetNamedDataSlot("appDataSlot"));
// Modify the StoredAppData structure
// When finished modifying this structure, store the changes back
// into the named data slot
Thread.SetData(Thread.GetNamedDataSlot("appDataSlot"),
storedAppData);
// Call another method that will use this structure
HandleStructure.MethodC( );
}
public static void MethodC( )
{
// Get the structure instance from the named data slot
Data storedAppData =
(Data)Thread.GetData(Thread.GetNamedDataSlot("appDataSlot"));
// Modify the storedAppData structure
// When finished modifying this structure, store the changes back into
// the named data slot
Thread.SetData(Thread.GetNamedDataSlot("appDataSlot"), storedAppData);
}
}
Discussion
Thread local storage is a convenient way to
store data that is usable across method calls without having to pass
the structure to the method or even without knowledge about where the
structure was actually created.
Data stored in a named TLS data slot is available only to that
thread; no other thread can access a named data slot of another
thread. The data stored in this data slot is accessible from anywhere
within the thread. This setup essentially makes this data global to
the thread.
To create a named data slot, use the static
Thread.GetNamedDataSlot
method. This method accepts a single parameter,
name, that defines the name of the data
slot. This name should be unique; if a data slot with the same name
exists, then the contents of that data slot will be returned and a
new data slot will not be created. This action occurs silently; there
is no exception thrown or error code available to inform you that you
are using a data slot someone else created. To be sure that you are
using a unique data slot, use the
Thread.AllocateNamedDataSlot
method. This method throws a
System.ArgumentException if a data slot already
exists with the same name. Otherwise, it operates similarly to the
GetNamedDataSlot method.
It is interesting to note that this named data slot is created on
every thread in the process, not just the thread that called this
method. This fact should not be much more than an inconvenience to
you, though, since the data in each data slot can be accessed only by
the thread that contains it. In addition, if a data slot with the
same name was created on a separate thread and you call
GetNamedDataSlot on the current thread with this
name, none of the data in any data slot on any thread will be
destroyed.
GetNamedDataSlot returns a
LocalDataStoreSlot
object that is used to access the data slot. Note that this class is
not creatable through the use of the new keyword.
It must be created through one of the AllocateDataSlot or
AllocateNamedDataSlot methods on the
Thread class.
To store data in this data slot,
use the static Thread.SetData method. This method
takes the object passed in to the data
parameter and stores it in the data slot defined by the
dataSlot parameter.
The static
Thread.GetData method retrieves the object stored
in a data slot. This method accepts a
LocalDataStoreSlot object that is created through
the Thread.GetNamedDataSlot method. The
GetData method then returns the object that was
stored in that particular data slot. Note that the object returned
might have to be cast to its original type before it can be used.
The static method
Thread.FreeNamedDataSlot will free the memory
associated with a named data slot. This method accepts the name of
the data slot as a string and, in turn, frees the
memory associated with that data slot. Remember that when a data slot
is created with GetNamedDataSlot, a named data
slot is also created on all of the other threads running in that
process. This is not really a problem when creating data slots with
the GetNamedDataSlot method because if a data slot
exists with this name, a LocalDataStoreSlot object
that refers to that data slot is returned, a new data slot is not
created, and the original data in that data slot is not destroyed.
This situation becomes more of a problem when using the
FreeNamedDataSlot method. This method will free
the memory associated with the data slot name passed in to it for all
threads, not just the thread that it was called on. Freeing a data
slot before all threads have finished using the data within that data
slot can be disastrous to your application.
A way to work around this problem is to not call the
FreeNamedDataSlot method at all. When a thread
terminates, all of its data slots in TLS are freed automatically. The
side effect of not calling FreeNamedDataSlot is
that the slot is taken up until the garbage collector determines that
the thread the slot was created on finished and the slot can be
freed.
If you know the number of TLS slots you need for your code at compile
time, consider using the ThreadStaticAttribute on
a static field of your class to set up TLS-like storage.
See Also
See the "Thread Local Storage and Thread Relative
Static Fields,"
"ThreadStatic-Attribute Attribute,"
and "Thread Class" topics in the
MSDN documentation.
|