The latest release of the Dynamics CRM 2011 SDK (v5.0.12) provides new guidance on improving service channel allocation performance. This guidance builds upon a previous recommendation to cache your references to service proxy objects such as OrganizationServiceProxy. While this recommendation still holds true, it presents an issue for another best-practice, implementing multi-threaded processes, because the proxy objects are not thread-safe.
Enter IServiceManagement<TService> and the ServiceConfigurationFactory.CreateManagement as well as new constructor overloads for the service proxy types. These capabilities now allow you to minimize expensive operations such as downloading endpoint metadata (WSDL) and authenticating (in claims/IFD/Online auth scenarios) by decoupling them from creating instances of the actual service proxy objects. Those operations are minimized because the references obtained can be safely cached and shared across threads. Below is an example based on the SDK documentation of how to use these new capabilities:
Read more about the latest CRM 2011 Development best-practices here: http://msdn.microsoft.com/en-us/library/gg509027#Performance
In addition to highlighting these exciting capabilities, I’d like to focus the remainder of this post on providing a few practical patterns for combining the above technique with implementing multi-threaded operations that interact with the CRM 2011 services.
These patterns will utilize both Data and Task Parallelism methods provided by the .NET 4.0 Task Parallel Library (TPL). The beauty of TPL is that the .NET framework provides a fluent means of adding parallelism/concurrency to applications while abstracting the developer from low-level implementation details. If you haven’t checked out this library and still implement your own ThreadPool mechanism, then you’re definitely missing out!
Read more about Parallel Programming in the .NET Framework here: http://msdn.microsoft.com/en-us/library/dd460693(v=vs.100)
To differentiate, Data Parallelism refers to performing the same operation concurrently against a partitioned source collection or arrays, whereas Task Parallelism refers to the queuing and dispatching of independent tasks that can be executed concurrently. Start to think about how typical operations that involve communication with the CRM 2011 services could fall into one of these two categories. When I think Data Parallelism, operations that immediately come to mind involve executing the same query over multiple paged result sets or Create/Update/Assign/Delete operations on a batch of entity records. For Task Parallelism, I think about scenarios that require retrieval of multiple entity types such as a cross-entity search or retrieving the true equivalent of a left-outer join query. These two scenarios both involve unique queries and handling of the result sets.
So, being able to leverage TPL, how do we keep our CRM service proxy objects aligned with individual threads when implementing concurrent operations? At first glance, it appears that service proxies would be instantiated and disposed of within the delegate method being executed. That isn’t exactly an optimal scenario since the same thread will likely perform multiple iterations thus creating and disposing of an unnecessary amount of proxy objects. Luckily, TPL provides the means to manage thread-local variables when implementing Data/Task Parallelism certain methods.
A quick aside, the patterns below use lambda expressions to define delegates in TPL. If you are not familiar with lambda expressions in C# or VB, review the following article before moving forward: http://msdn.microsoft.com/en-us/library/dd460699(v=vs.100)
Data Parallelism: Parallel.For<TLocal> and Parallel.ForEach<TSource, TLocal> with thread-local OrganizationServiceProxy variable
Our first two concurrent patterns will involve the Data Parallelism methods Parallel.For and Parallel.ForEach. These methods are similar in nature to standard for and foreach iteration statements respectively. The major difference is that the iterations are partitioned and executed concurrently in a manner that makes optimal use of available system resources.
Both methods offer generic constructor overloads that allow you to specify a thread-local type, define a delegate action to instantiate your thread-local variable, and another delegate action to handle any final operations that should be performed after the thread completes all of its iterations. To properly align CRM service proxy objects with threads we’ll incorporate proxy instantiation and disposal into this overload. (Note that the term “thread-local” is slightly inaccurate, because in some cases two partitions may be executed on the same thread. Again, TPL makes these optimization decisions on our behalf and is an acceptable scenario given that we’re still minimizing service proxy objects.)
Let’s start with the Parallel.For<OrganizationServiceProxy> loop pattern. The code sample below illustrates this pattern in the context of concurrently retrieving paged query results. The first delegate (Action) returns a new OrganizationServiceProxy, instantiated using our cached IServiceManagement<IOrganizationService> reference and potentially via pre-authenticated credentials.
The second delegate (Func<TResult>) accepts multiple parameters including a reference to our thread-local service proxy. The delegate body contains the primary operation to be executed and it is supplied with three parameters: index, loopState, and our thread-local proxy reference. In our example scenario, we’re handling paged queries, thus the index parameter can supply the necessary query PagingInfo and the proxy reference allows us to execute the RetrieveMultiple request. The proxy reference must be returned at conclusion of the operation so that it can be passed to the next iteration. This is useful to ensure any changes to the thread-local variable are carried forward to subsequent iterations, but is not directly relevant in our service proxy scenario other than satisfying the intended implementation.
Lastly, cleanup of the service proxy object is performed in the third delegate (Action<T>). The name of this delegate parameter, ‘localFinally’ suggests a try-finally statement where resources are assured release in the finally block. It indeed behaves similarly in that unhandled exceptions in the body delegate are wrapped into an AggregateException type and the localFinally delegate is always executed. Thus, we’ll use this delegate Action to dispose of our service proxy reference after the thread has completed all of its assigned work.
Read more about how to write a Parallel.For loop with thread-local variables here: http://msdn.microsoft.com/en-us/library/dd460703(v=vs.100).aspx
For our Parallel.ForEach<TSource, OrganizationServiceProxy> pattern, the below code sample illustrates using a thread-local proxy reference to submit Create requests for a list of new CRM Entity objects. Notice that the structure of Parallel.ForEach loop is very similar to Parallel.For loop from the perspective of handling a thread-local variable and defining the primary operation. The only difference is that we instead supply an IEnumerable<TSource> where each item is targeted as an iteration rather than performing a specified number of loops. We also have the ability to update the current enumerated item submitted to the loop body delegate. In the example below, we are assigning the Entity.Id property from the System.Guid value returned as the Create response.
Read more about how to write a Parallel.ForEach loop with thread-local variables here: http://msdn.microsoft.com/en-us/library/dd460703(v=vs.100)
Task Parallelism: Parallel.Invoke with ThreadLocal<T> OrganizationServiceProxy
Our third pattern involves Task Parallelism, meaning it’s appropriate when needing to execute multiple, independent operations concurrently. In this pattern we’ll focus on the use of Parallel.Invoke(Action actions) method. Nevertheless, TPL also provides the ability to create and run Tasks explicitly providing more control over nesting and chaining Tasks.
The Parallel.Invoke method only accepts an array of delegate actions, so we can utilize a ThreadLocal<T> instance to align our OrganizationServiceProxy with each concurrent thread. Be sure to instantiate the ThreadLocal<T> instance inside a using statement or explicitly call .Dispose() to release resources held by this reference. Since we must also dispose of our service proxy objects, we’ll track each instance created in a ConcurrentBag<T> that we can reference after executing our concurrent operations.
Within each delegate Action, we can safely access the ThreadLocal<T>.Value to get the reference to our service proxy object. Again, each of these actions should be independent of each other. There is not guarantee as to the execution order or even whether they execute in parallel.
Once all of the concurrent operations are complete, we revisit our proxyBag tracking object in the finally block to cleanup up each of the service proxy objects that were created. An more sophisticated approach involving a wrapper class to ThreadLocal<T> is provided here: