Parallel Programming Notes - Adapting Object-Oriented Patterns
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
back to index
n4jvp.com