The purpose of the Spring.Threading namespace
is to provide a place to keep useful concurrency abstractions that augment
those in the BCL. Since Doug Lea has provided a wealth of mature public
domain concurrency abstractions in his Java based
'EDU.oswego.cs.dl.util.concurrent' libraries we decided to port a few of
his abstractions to .NET. So far, we've only ported three classes, the
minimum necessary to provide basic object pooling functionality to support
an AOP based pooling aspect and to provide a Semaphore class that was
mistakenly not included in .NET 1.0/1.1.
As a side note, the classes present in the 'java.util.concurrent' package of Java 5 were based on the aforementioned libraries and underwent additional scrutiny and peer review. The Java 5 libraries and the new additions to the threading namespace in .NET 2.0 are potential sources for modifications to Spring's threading support.
There is also the important utility class, LogicalThreadContext, for performing thread local storage.
Depending on your runtime environment there are different strategies
to use for storing objects in thread local storage. If you are in web
applications a single Request may be executed on different threads. As
such, the location to store thread local objects is in
HttpContext.Current. For other environments
System.Runtime.Remoting.Messaging.CallContext is
used. For more background information on the motivation behind these
choices, say as compared the the attribute [ThreadStatic] refer to
"Piers7"'s blog
and this forum
post. The class LogicalThreadContext is
used to hide these details so you can have more portability in your code
across runtime environments.
The API is quite simple and shown below
public sealed class LogicalThreadContext
{
public static object GetData(string name)
public static void SetData(string name, object value)
public static void FreeNamedDataSlot(string name)
}
The class has static methods and a string key name to get and set the data. If you want to free up the storage area then use the method FreeNamedDataSlot.
When you take a look at these synchronization classes, you'll wonder
why it's even necessary when System.Threading provides
plenty of synchronization options. Although
System.Threading provides great synchronization
classes, it doesn't provide well-factored abstractions and interfaces for
us. Without these abstractions, we will tend to code at a low-level. With
enough experience, you'll eventually come up with some abstractions that
work well. Doug Lea has already done a lot of that research and has a
class library that we can take advantage of.
ISync is the central interface for all classes
that control access to resources from multiple threads. It's a simple
interface which has two basic use cases. The first case is to block
indefinitely until a condition is met:
void ConcurrentRun(ISync lock) {
lock.Acquire(); // block until condition met
try {
// ... access shared resources
}
finally {
lock.Release();
}
}
The other case is to specify a maximum amount of time to block before the condition is met:
void ImpatientConcurrentRun(ISync lock) {
// block for at most 10 milliseconds for condition
if ( lock.Attempt(10) ) {
try {
// ... access shared resources
}
finally {
lock.Release();
}
} else {
// complain of time out
}
}
The SyncHolder class implements the
System.IDisposable interface and so provides a way to
use an ISync with the using C#
keyword: the ISync will be automatically
Acquired and then Released on
exiting from the block.
This should simplify the programming model for code using (!) an
ISync:
ISync sync = ...
...
using (new SyncHolder(sync))
{
// ... code to be executed
// holding the ISync lock
}
There is surely the timed version, a little more cumbersome as you must deal with timeouts:
ISync sync = ...
long msecs = 100;
...
// try to acquire the ISync for msecs milliseconds
try
{
using (new SyncHolder(sync, msecs))
{
// ... code to be executed
// holding the ISync lock
}
}
catch (TimeoutException)
{
// deal with failed lock acquisition
}
The Latch class implements the
ISync interface and provides an implementation of a
latch. A latch is a boolean condition that is set
at most once, ever. Once a single release is issued, all acquires will
pass. It is similar to a ManualResetEvent initialized
unsignalled (Reset) and can only be Set(). A typical
use is to act as a start signal for a group of worker threads.
class Boss {
Latch _startPermit;
void Worker() {
// very slow worker initialization ...
// ... attach to messaging system
// ... connect to database
_startPermit.Acquire();
// ... use resources initialized in Mush
// ... do real work
}
void Mush() {
_startPermit = new Latch();
for (int i=0; i<10; ++i) {
new Thread(new ThreadStart(Worker)).Start();
}
// very slow main initialization ...
// ... parse configuration
// ... initialize other resources used by workers
_startPermit.Release();
}
}The Semaphore class implements the
ISync interface and provides an implementation of a
semaphore. Conceptually, a semaphore maintains a set of permits. Each
Acquire() blocks if necessary until a permit is
available, and then takes it. Each Release() adds a
permit. However, no actual permit objects are used; the Semaphore just
keeps a count of the number available and acts accordingly. A typical
use is to control access to a pool of shared objects.
class LimitedConcurrentUploader {
// ensure we don't exceed maxUpload simultaneous uploads
Semaphore _available;
public LimitedConcurrentUploader(maxUploads) {
_available = new Semaphore(maxUploads);
}
// no matter how many threads call this method no more
// than maxUploads concurrent uploads will occur.
public Upload(IDataTransfer upload) {
_available.Acquire();
try {
upload.TransferData();
}
finally {
_available.Release();
}
}
}