Applications like Windows Services and Web Services often
have a demanding workload and sophisticated extensibility needs. Managing a
workload on the .NET platform often
means utilizing the Task Parallel
Library (TPL) classes. .NET extensibility capabilities span many
components, but increasingly popular extensibility features are found in the Managed
Extensibility Framework (MEF). Bringing TPL and MEF together is this
article’s focus. Walking through a sample application, I’ll demonstrate a
loosely coupled and extensible TPL and MEF solution.
MEF – CompositonContainers, Catalogs, and Attributes
A complete MEF introduction is beyond the scope of this
article. So the overview will center on some main ideas. You’ll find a more
complete MEF introduction in the Resources section at the end of the article.
MEF includes classes and attributes that support component
discovery and extensibility. MEF allows applications to load components
without baking references into the compiled application. Two simple ideas
underlie MEF solutions.
- Components providing functionality are called “Exports”.
- Components “consuming” an “Export” are called “Imports”.
So, for example, a class within an assembly may “export”
functionality that another class in another assembly “imports”.
Some core MEF components are described below:
- Import and Export attributes designate the components a developer
will consume or provide. - A CompositionContainer gathers the exports and matches exports to
imports. CompositionContainers can, for example, instantiate class instances
and apply the instance on a class Property. - CompositionContainers read from Catalogs. MEF components can be
housed in Directories and Assemblies. A CompositionContainer works through,
for example, a DirectoryCatalog to gather all MEF components in a Directory.
The following code from the sample solution demonstrates how
CompositionContainers, Catalogs, and Attributes work together.
[ImportMany(typeof(ITaskInstanceFactory))] private IEnumerable<ITaskInstanceFactory> Instances { get; set; } public Host(string extLocation) { this.Instances = null; var catalog = new DirectoryCatalog(extLocation); using (var container = new CompositionContainer(catalog)) { try { //This will populate the Instance Import container.ComposeParts(this); } catch (CompositionException compositionException) { Console.WriteLine(compositionException.ToString()); } } } [Export(typeof(ITaskInstanceFactory))] public class SupplyTaskInstances2 : ITaskInstanceFactory
ITaskInstanceFactory and IRequest interfaces follow below.
public interface ITaskInstanceFactory { Task<TReply> StartNewOne<TRequest, TReply>(TRequest request, CancellationToken token) where TRequest : IRequest; /// <summary> /// An instance may support more than one /// </summary> List<int> IDs { get; } } public interface IRequest { int ID { get; } }
The Host class Instances Property Imports an
ITaskInstanceFactory collection and the Import is satisfied from the Exported
SupplyTaskInstance class. CompositionContainer ComposeParts method applies the
instances to the Instances Property on the Host class.
MEF’s role in the solution may seem obvious. A highly decoupled
solution could not exist if applications had to maintain references to one
another. The role TPL plays in the solution is more subtle.
TPL Overview
The core of TPL is embodied in the Task. A complete Task
overview can be found in the resources at the end of this article. I like to
think of Tasks as the chunks of work an application performs. If you’re
familiar with Threading and the Thread Pool; think of Tasks as an extension on
the Thread Pool concept. Tasks are a context for an Action or Funct delegate.
Tasks encapsulate the operation Result and the operation failure. Tasks can
be chained together in Continuations. Tasks also support Cancellation. Task
Parallel Library (TPL) includes TaskSchedulers that apply heuristics
orchestrating a Task collection workload.
Developers who want to embrace all the power on a modern CPU
need compose a workload into Tasks. Future Language feature enhancements will
make developing with Tasks easier, but the enhancements will not eliminate the
need to think about problems like, for example, shared memory.
Following is Task usage code from the sample application.
if (typeof(TRequest) == typeof(Request3)) { funct = new Func<TReply>(() => { Console.WriteLine("Starting " + request.ID.ToString()); SpinWait.SpinUntil(() => { return false; }, 3000); Console.WriteLine("Completed 3 second Task"); return (TReply)(object)new Reply3(); } ); } task = new Task<TReply>(funct); task.Start(); return task;
The code demonstrates allocating and Starting a Task. As
stated earlier, Tasks are queued to a Scheduler and run Asynchronously. In the
following example; a Task consumer waits for the multiple Tasks to complete.
Task<Reply1> t1 = null; Task<Reply2> t2 = null; Task<Reply3> t3 = null; int itr = 3; if (Directory.Exists(extLocation)) { for (int i = 0; i < itr; ++i) { t1 = host.StartNewOne<Request1, Reply1>(new Request1(), src.Token); t2 = host.StartNewOne<Request2, Reply2>(new Request2(), src.Token); t3 = host.StartNewOne<Request3, Reply3>(new Request3(), src.Token); var tList = new Task[3] { t1, t2, t3 }; Task.WaitAll(tList); Console.WriteLine("Completed iteration " + i.ToString()); }
At first glance, Tasks may appear to be overkill for this
simple solution. To understand the compositional power within Tasks it may
help to contrast it with a Method based solution.
Tasks vs. Methods
Why even consider using Tasks in the first place? This
solution could have easily been implemented with delegates or purely generic
methods. Most likely, a Windows Service or Web Service would’ve required some sort
of Threading construct. That could mean a number of options. A Windows
Service could have used the Thread Pool and a WCF Web Service could have relied
on WCF instancing. So, once a developer begins to think about Threads; a pure
Method solution begins looking like a Task based solution.
Taking the Web Service example; ideally a developer should
be using WCF’s Asynchronous Programming Model (APM). Synchronous Web Services
tie up IO resources waiting for operations to complete. APM solutions
interleave IO resources consuming fewer server resources. Tasks support the
APM model. In fact a Task nicely encapsulates an IAsyncResult.
An application requiring MEF would not be trivial. Instead
of a handful of operations; a likely solution would have hundreds of
operations. Many of these operations may be interdependent on one another.
So, for example, a developer may submit multiple operations and wait for them
all to complete. Operations may impose time limits with varying values.
Anticipating all the overload options on a method is not feasible. Most of
what is needed is provided in a Task. What is not provided in a Task can be
implemented using, for example, Continuations or Cancellations.
Conclusion
MEF provides some simple, but powerful extensibility
options. TPL has been built for managing workloads on modern CPUs. Together
they’re a potent workload indirection solution.
Resources
MEF Introductions
A
Peek into The Managed Extensibility Framework (MEF) in .NET Framework 4.0
Managed
Extensibility Framework Overview
Task Parallel Library
Understanding
Tasks in .NET Framework 4.0 Task Parallel Library