Structural Patterns
Facade
supplies simplified view of larger system
hide complexity of parallelism from other parts of an application
protects from side-effects
facade can easily be refactored to allow different implementations of functionality
without altering calling code
Decorators
decorator pattern overrides behavior of underlying class
can decorate serial implementations to create parallel ones
public interface IImageEditor
{
void Rotate(RotateFlipType rotation, IEnumerable<Bitmap> images);
}
public class SerialEditor : IImageEditor
{
public void Rotate(RotateFlipType rotation, IEnumerable<Bitmap> images)
{
foreach(Bitmap b in images)
b.RotateFlip(rotation);
}
}
public class ParallelEditor : IImageEditor
{
private IImageEditor decorated;
public ParallelEditor(IImageEditor decorated)
{
this.Decorated = decorated;
}
public void Rotate(RotateFlipType rotation, IEnumerable<Bitmap> images)
{
if (decorated == null)
return;
Parallel.Foreach(images, b => { b.RotateFlip(rotation); });
}
}
pass instance of SerialEditor as argument to ParallelEditor
behavior of Rotate method is changed
IList<Bitmap> images = ...
IImageEditor parallel = new ParallelEditor(new SerialEditor());
parallel.Rotate(RotateFlipType.RotateNoneFlipX, images);
encapsulates parallelism while preseving existing interface, calling code remains
largely unchanged
allows different parallel implementations to be used without altering the calling
code
separates concerns, decorated type implements work being done, decorator responsible
for parallelizing the work
Adapters
adapters translate from one interface to another
adapt event-based interface with interface that uses features
Public interface IWithFutures
{
Task<int> Start();
}
public class FuturesBased : IWithFutures
{
public Task<int> Start()
{
return Task<int>.Factory.StartNew(() => { ... });
}
}
public interface IWithEvents
{
void Start();
event EventHandler<CompletedEventArgs> Completed;
}
public class EventBased : IWithEvents
{
readonly IWithFutures
IWithFutures instance = new FuturesBased();
public void Start()
{
Task<int> task = instance.Start();
task.ContinueWith((t) =>
{
var evt = Completed;
if(evt != null)
evt(this, new CompletedEventArgs(t.Result);
}
}
public event EventHandler<CompletedEventArgs> Completed;
}
event-based implementation adapts the IWithFutures interface allowing results to
be handled by an event handler
IWithEvents model = new EventBased():
bool completed = false;
// assign event handler
model.Completed += (s, e) =>
{
Console.WriteLine("Completed event : result = {0}", e.Result);
completed = true;
};
// start model and wait for Completed event
model.Start();
Repositories & Parallel Data Access
mediates application logic and data access layers
sort of a facade for data sources
don't share connections between tasks
keep connections as short as possible
using tasks to open multiple connections to the same database may have significant
performance implications
Singletons & Service Locators
singleton is a class with a single instance
service locators are singletons that control object resolution or that map interfaces
to specific implementations at run time
when singletons & service locators are used in an app, it's probable that there's
some form fo shared state
Implementing a Singleton with the Lazy<T> Type
public sealed class LazySingleton
{
private readonly static Lazy<LazySingleton> instance = new Lazy<LazySingleton>(() => new LazySingleton())
private LazySingleton() { }
public static LazySingleton Instance { get { return instance.Value; }
}
provides thread-safe way to resolve instance of a class
doesn't make type thread-safe
most DI containers such as Unity support thread-safe resolution but not thread-safe
configuration
Immutable Types
requirements
- must contain only fields that aren't modified outside of the constructor,
readonly
keyword enforces this at compile time
- must contain only fields that are immutable types
- must only inherit from immutable type
- can't be inherited by mutable types, use
sealed keyword to prevent virtual
overrides
- can't publish references to itself (this pointer) during construction
Immutable Types as Value Types
value types use copy semantics for assignment
two values are equal if all of their corresponding fields are equal
if values are equal hashcodes are equal
classes are reference types
instances are equal only if they're the result of the same invocation of the
new
operator
make immutable type behave like value type by overriding Equals method
must also override GetHashCode method
advisible to override same as (==) operator
Shared Data Classes
System.Collections.Concurrent namespace contains several thread-safe data structures
Type
|
Description
|
BlockingCollection<T>
|
implements both a bounded and unbounded producer/consumer
|
ConcurrentBag<T>
|
unordered collection of objects
|
ConcurrentDictionary<T>
|
as named
|
ConcurrentQueue<T>
|
FIFO non-blocking queue
|
ConcurrentStack<T>
|
LIFO stack
|
limit sharing or avoid it entirely
where possible use shared data collections in preference to locks
use .NET shared data classes in preference to user-defined types
Iterators
used to control flow of another method
// class
class Tree<T>
{
public Tree<T> Left, Right;
public T Data;
}
// custom iterator
public IEnumerable<Tree<T>> Iterate<T>()
{
var queue = new Queue<Tree<T>>();
queue.Enqueue(this);
while (queue.Count > 0)
{
var node = queue.Dequeue();
yield return node;
if (node.Left != null) queue.Enqueue(node.Left);
if (node.Right != null) queue.Enqueue(node.Right);
}
}
// usage
Tree<T> myTree = ...
Parallel.Foreach(myTree.Iterator(), node => { ... });
Lists & Enumerables
IList<T> defines functionality of indexed lists
IEnumerable<T> is used for unindexed iteration
in rare cases parallel loop's default handling of IList<T> implementation
may not be suitable
- unfavorable random-access performance characteristics
- races conditions from several threads attempting to perform lazy loading at the
same time
need to override Parallel.ForEach's default handling of a source that provides the
IList<T> interface
Parallel.ForEach requires its source to implement IEnumerable<T>
method looks for IList<T> and uses that interface if found