Initial import!
This commit is contained in:
279
doc/reference/src/pooling-example.xml
Normal file
279
doc/reference/src/pooling-example.xml
Normal file
@@ -0,0 +1,279 @@
|
||||
<sect1>
|
||||
<title>Pooling example</title>
|
||||
<para>
|
||||
The idea is to build an executor backed by a pool of
|
||||
<literal>QueuedExecutor</literal>: this will show how Spring.NET
|
||||
provides some useful low-level/high-quality reusable threading and
|
||||
pooling abstractions.
|
||||
This executor will provide parallel executions (in our case
|
||||
<literal>grep</literal>-like file scans). <emphasis>Note: This example
|
||||
is not in the 1.0.0 release to its use of classes in the Spring.Threading
|
||||
namespace scheduled for release in Spring 1.1. To access ths example
|
||||
please get the code from CVS <ulink url="http://opensource.atlassian.com/confluence/spring/display/NET/Project+Structure">(instructions)</ulink> or from the download section of the
|
||||
Spring.NET website that contains an .zip with the full CVS tree.</emphasis>
|
||||
</para>
|
||||
<para>
|
||||
Some information on <literal>QueuedExecutor</literal> is helpful to
|
||||
better understand the implementation and to possibly disagree with it.
|
||||
Keep in mind that the point is to show how to develop your own
|
||||
object-pool.
|
||||
</para>
|
||||
<para>
|
||||
A <literal>QueuedExecutor</literal> is an executor where
|
||||
<literal>IRunnable</literal> instances are run serialy by a worker
|
||||
thread. When you <literal>Execute</literal> with a
|
||||
<literal>QueuedExecutor</literal>, your request is queued; at some
|
||||
point in the future your request will be taken and executed by the
|
||||
worker thread: in case of error the thread is terminated.
|
||||
However
|
||||
this executor recreates its worker thread as needed.
|
||||
</para>
|
||||
<para>Last but not least, this executor can be shut down in
|
||||
a few different ways (please refer to the Spring.NET SDK documentation).
|
||||
Given its simplicity, it is very powerful.
|
||||
</para>
|
||||
<para>
|
||||
The example project <literal>Spring.Examples.Pool</literal> provides
|
||||
an implementation of a pooled executor, backed by n instances of
|
||||
<literal>Spring.Threading.QueuedExecutor</literal>: please ignore
|
||||
the fact that <literal>Spring.Threading</literal> includes already a
|
||||
very different implementation of a <literal>PooledExecutor</literal>:
|
||||
here we wanto to use a pool of <literal>QueuedExecutor</literal>s.
|
||||
</para>
|
||||
<para>
|
||||
This executor will be used to implement a parallel
|
||||
recursive <literal>grep</literal>-like console executable.
|
||||
</para>
|
||||
<sect2>
|
||||
<title>Implementing <literal>Spring.Pool.IPoolableObjectFactory</literal></title>
|
||||
<para>
|
||||
In order to use the <literal>SimplePool</literal> implementation,
|
||||
the first thing to do is to implement the <literal>IPoolableObjectFactory</literal>
|
||||
interface. This interface is intended to be implemented by objects
|
||||
that can create the type of objects that should be pooled.
|
||||
The <literal>SimplePool</literal>
|
||||
will call the lifecycle methods on <literal>IPoolableObjectFactory</literal> interface
|
||||
(<literal>MakeObject, ActivateObject, ValidateObject, PassivateObject, and DestroyObject</literal>)
|
||||
as appropriate when the pool is created, objects are borrowed and returned to the pool, and when
|
||||
the pool is destroyed.
|
||||
</para>
|
||||
<para>
|
||||
In our case, as already said, we want to to implement a pool
|
||||
of <literal>QueuedExecutor</literal>. Ok, here the declaration:
|
||||
<programlisting format='linespecific' xml:space='preserve'>
|
||||
public class QueuedExecutorPoolableFactory : IPoolableObjectFactory
|
||||
{</programlisting>
|
||||
the first task a factory should do is to create objects:
|
||||
<programlisting format='linespecific' xml:space='preserve'>
|
||||
object IPoolableObjectFactory.MakeObject()
|
||||
{
|
||||
// to actually make this work as a pooled executor
|
||||
// use a bounded queue of capacity 1.
|
||||
// If we don't do this one of the queued executors
|
||||
// will accept all the queued IRunnables as, by default
|
||||
// its queue is unbounded, and the PooledExecutor
|
||||
// will happen to always run only one thread ...
|
||||
return new QueuedExecutor(new BoundedBuffer(1));
|
||||
}</programlisting>
|
||||
and should be also able to destroy them:
|
||||
<programlisting format='linespecific' xml:space='preserve'>
|
||||
void IPoolableObjectFactory.DestroyObject(object o)
|
||||
{
|
||||
// ah, self documenting code:
|
||||
// Here you can see that we decided to let the
|
||||
// executor process all the currently queued tasks.
|
||||
QueuedExecutor executor = o as QueuedExecutor;
|
||||
executor.ShutdownAfterProcessingCurrentlyQueuedTasks();
|
||||
}</programlisting>
|
||||
</para>
|
||||
<para>
|
||||
When an object is taken from the pool, to satisfy a client request,
|
||||
may be the object should be activated. We can possibly implement the
|
||||
activation like this:
|
||||
<programlisting format='linespecific' xml:space='preserve'>
|
||||
void IPoolableObjectFactory.ActivateObject(object o)
|
||||
{
|
||||
QueuedExecutor executor = o as QueuedExecutor;
|
||||
executor.Restart();
|
||||
}</programlisting>
|
||||
even if a <literal>QueuedExecutor</literal> restarts itself as
|
||||
needed and so a valid implementation could leave this method empty.
|
||||
</para>
|
||||
<para>
|
||||
After activation, and before the pooled object can be succesfully
|
||||
returned to the client, it is validated (should the object be
|
||||
invalid, it will be discarded: this can lead to an empty unusable
|
||||
pool
|
||||
<footnote>
|
||||
<para>You may think that we can provide a smarter
|
||||
implementation and you are probably right. However, it is not so
|
||||
difficult to create a new pool in case the old one became unusable.
|
||||
It could not be your preferred choice but surely it leverages
|
||||
simplicity and object immutability
|
||||
</para>
|
||||
</footnote>).
|
||||
Here we check that the worker thread exists:
|
||||
<programlisting format='linespecific' xml:space='preserve'>
|
||||
bool IPoolableObjectFactory.ValidateObject(object o)
|
||||
{
|
||||
QueuedExecutor executor = o as QueuedExecutor;
|
||||
return executor.Thread != null;
|
||||
}</programlisting>
|
||||
</para>
|
||||
<para>
|
||||
Passivation, symmetrical to activation, is the process a pooled
|
||||
object is subject to when the object is returned to the pool. In our
|
||||
case we simply do nothing:
|
||||
<programlisting format='linespecific' xml:space='preserve'>
|
||||
void IPoolableObjectFactory.PassivateObject(object o)
|
||||
{
|
||||
}</programlisting>
|
||||
</para>
|
||||
<para>
|
||||
At this point, creating a pool is simply a matter of creating an
|
||||
<literal>SimplePool</literal> as in:
|
||||
<programlisting format='linespecific' xml:space='preserve'>
|
||||
pool = new SimplePool(new QueuedExecutorPoolableFactory(), size);</programlisting>
|
||||
</para>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>Being smart using pooled objects</title>
|
||||
<para>
|
||||
Taking advantage of the <literal>using</literal> keyword seems
|
||||
to be very important in these <literal>c#</literal> days, so we
|
||||
implement a very simple helper (<literal>PooledObjectHolder</literal>)
|
||||
that can allow us to do things like:
|
||||
<programlisting format='linespecific' xml:space='preserve'>
|
||||
using (PooledObjectHolder holder = PooledObjectHolder.UseFrom(pool))
|
||||
{
|
||||
QueuedExecutor executor = (QueuedExecutor) holder.Pooled;
|
||||
executor.Execute(runnable);
|
||||
}</programlisting>
|
||||
without worrying about obtaining and returning an object from/to the
|
||||
pool.
|
||||
</para>
|
||||
<para>
|
||||
Here is the implementation:
|
||||
<programlisting format='linespecific' xml:space='preserve'>
|
||||
public class PooledObjectHolder : IDisposable
|
||||
{
|
||||
IObjectPool pool;
|
||||
object pooled;
|
||||
|
||||
/// <summary>
|
||||
/// Builds a new <see cref="PooledObjectHolder"/>
|
||||
/// trying to borrow an object form it
|
||||
/// </summary>
|
||||
/// <param name="pool"></param>
|
||||
private PooledObjectHolder(IObjectPool pool)
|
||||
{
|
||||
this.pool = pool;
|
||||
this.pooled = pool.BorrowObject();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allow to access the borrowed pooled object
|
||||
/// </summary>
|
||||
public object Pooled
|
||||
{
|
||||
get
|
||||
{
|
||||
return pooled;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the borrowed object to the pool
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
pool.ReturnObject(pooled);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="PooledObjectHolder"/> for the
|
||||
/// given pool.
|
||||
/// </summary>
|
||||
public static PooledObjectHolder UseFrom(IObjectPool pool)
|
||||
{
|
||||
return new PooledObjectHolder(pool);
|
||||
}
|
||||
}</programlisting>
|
||||
</para>
|
||||
<para>
|
||||
Please don't forget to destroy all the pooled istances once you have
|
||||
finished! How? Well using something like this in
|
||||
<literal>PooledQueuedExecutor</literal>:
|
||||
<programlisting format='linespecific' xml:space='preserve'>
|
||||
public void Stop ()
|
||||
{
|
||||
// waits for all the grep-task to have been queued ...
|
||||
foreach (ISync sync in syncs)
|
||||
{
|
||||
sync.Acquire();
|
||||
}
|
||||
pool.Close();
|
||||
}</programlisting>
|
||||
</para>
|
||||
</sect2>
|
||||
<sect2>
|
||||
<title>Using the executor to do a parallel <literal>grep</literal></title>
|
||||
<para>
|
||||
The use of the just built executor is quite straigtforward but a
|
||||
little tricky if we want to really exploit the pool.
|
||||
<programlisting format='linespecific' xml:space='preserve'>
|
||||
private PooledQueuedExecutor executor;
|
||||
|
||||
public ParallelGrep(int size)
|
||||
{
|
||||
executor = new PooledQueuedExecutor(size);
|
||||
}
|
||||
|
||||
public void Recurse(string startPath, string filePattern, string regexPattern)
|
||||
{
|
||||
foreach (string file in Directory.GetFiles(startPath, filePattern))
|
||||
{
|
||||
executor.Execute(new Grep(file, regexPattern));
|
||||
}
|
||||
foreach (string directory in Directory.GetDirectories(startPath))
|
||||
{
|
||||
Recurse(directory, filePattern, regexPattern);
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
executor.Stop();
|
||||
}</programlisting>
|
||||
</para>
|
||||
<para>
|
||||
<programlisting format='linespecific' xml:space='preserve'>
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
if (args.Length < 3)
|
||||
{
|
||||
Console.Out.WriteLine("usage: {0} regex directory file-pattern [pool-size]", Assembly.GetEntryAssembly().CodeBase);
|
||||
Environment.Exit(1);
|
||||
}
|
||||
|
||||
string regexPattern = args[0];
|
||||
string startPath = args[1];
|
||||
string filePattern = args[2];
|
||||
int size = 10;
|
||||
try
|
||||
{
|
||||
size = Int32.Parse(args[3]);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
Console.Out.WriteLine ("pool size {0}", size);
|
||||
|
||||
ParallelGrep grep = new ParallelGrep(size);
|
||||
grep.Recurse(startPath, filePattern, regexPattern);
|
||||
grep.Stop();
|
||||
}</programlisting>
|
||||
</para>
|
||||
</sect2>
|
||||
</sect1>
|
||||
|
||||
Reference in New Issue
Block a user