AXForum  
Вернуться   AXForum > Microsoft Dynamics CRM > Dynamics CRM: Blogs
All
Забыли пароль?
Зарегистрироваться Правила Справка Пользователи Сообщения за день Поиск Все разделы прочитаны

 
 
Опции темы Поиск в этой теме Опции просмотра
Старый 12.09.2012, 00:11   #1  
Blog bot is offline
Blog bot
Участник
 
25,459 / 846 (79) +++++++
Регистрация: 28.10.2006
crminthefield: Optimize CRM 2011 Service Channel allocation for multi-threaded processes using .NET Task Parallel Library (TPL) and a thread-local service proxy
Источник: http://blogs.msdn.com/b/crminthefiel...ice-proxy.aspx
==============

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 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:

//Perform metadata (WSDL) download once and re-use across multiple OrganizationServiceProxy connections var orgServiceManagement = ServiceConfigurationFactory .CreateManagement(new Uri(organizationUrl)); //If claims-based/IFD or CRM Online auth scenarios, obtain a reusable SecurityTokenResponse via Authenticate() method var authCredentials = orgServiceManagement .Authenticate(new AuthenticationCredentials(credentials)); 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 and Parallel.ForEach 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 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 reference and potentially via pre-authenticated credentials.

The second delegate (Func) 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). 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.

//*SCENARIO*: The number of query pages to be retrieved concurrently via Parallel.For var pageCount = 10; //would typically represent the ceiling of row count/pageSize var pageSize = 5000; try { Parallel.For(1, pageCount, new ParallelOptions() { MaxDegreeOfParallelism = maxDop }, () => { //Setup a client proxy for each thread - the following assumes AD authentication var proxy = new OrganizationServiceProxy(orgServiceManagement, credentials); //*TIP* For claims-based/IFD and CRM Online re-use a pre-authed security token for each proxy connection //var proxy = OrganizationServiceProxy(orgServiceManagement, // authCredentials.SecurityTokenResponse); //*OPTIONAL* Enable early-bound entities //proxy.EnableProxyTypes(); return proxy; }, (index, loopState, proxy) => { //Do iterative work here using the thread-local proxy //*EXAMPLE* usage of thread-local loop index to define query page //var page = new PagingInfo() //{ // Count = pageSize, // PageNumber = index //}; //*EXAMPLE* usage thread-local proxy to execute request //var results = proxy.RetrieveMultiple(query); //*TIP* To consolidate results across threads, add to a concurrent collection //type defined in the System.Collections.Concurrent namespace //return the client proxy to the next iteration in the current thread's loop return proxy; }, (proxy) => { //Dispose of proxy in thread's localFinally Action proxy.Dispose(); proxy = null; }); } catch (AggregateException ae) { foreach (var ex in ae.InnerExceptions) { //Handle exceptions } }

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 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 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.

//*SCENARIO*: A list of entities to be created concurrently via Parallel.ForEach var entities = new List(); try { Parallel.ForEach(entities, new ParallelOptions() { MaxDegreeOfParallelism = maxDop }, () => { //Setup a client proxy for each thread - the following assumes AD authentication var proxy = new OrganizationServiceProxy(orgServiceManagement, credentials); //*TIP* For claims-based/IFD and CRM Online re-use a pre-authed security token for each proxy connection //var proxy = OrganizationServiceProxy(orgServiceManagement, // authCredentials.SecurityTokenResponse); //*OPTIONAL* Enable early-bound entities //proxy.EnableProxyTypes(); return proxy; }, (e, loopState, index, proxy) => { //Do iterative work on the source items here using the thread-local proxy //*EXAMPLE* usage of thread-local proxy execute request //e.Id = proxy.Create(e); //return the client proxy to the next iteration in the current thread's loop return proxy; }, (proxy) => { //Dispose of proxy in thread's localFinally Action proxy.Dispose(); proxy = null; }); } catch (AggregateException ae) { foreach (var ex in ae.InnerExceptions) { //Handle exceptions } } 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 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 instance to align our OrganizationServiceProxy with each concurrent thread. Be sure to instantiate the ThreadLocal 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 that we can reference after executing our concurrent operations.

Within each delegate Action, we can safely access the ThreadLocal.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 is provided here: http://stackoverflow.com/questions/7...ocalidisposabl

Note that in .NET 4.5, a new collection property, ThreadLocal.Values, has been added. If a new ThreadLocal is constructed specifying ‘trackAllValues’ = true, then this collection can be accessed to obtain references to each thread-specific instance created over the life of the ThreadLocal object.

//*SCENARIO*: Perform multiple, unique entity type queries concurrently var proxyBag = new ConcurrentBag(); using (var threadLocal = new ThreadLocal( () => { //Setup a client proxy for each thread - the following assumes AD authentication var proxy = new OrganizationServiceProxy(orgServiceManagement, credentials); //*TIP* For claims-based/IFD and CRM Online re-use a pre-authed security token for each proxy connection //var proxy = OrganizationServiceProxy(orgServiceManagement, // authCredentials.SecurityTokenResponse); //*OPTIONAL* Enable early-bound entities //proxy.EnableProxyTypes(); //Track the proxy in our ConcurrentBag for disposal purposes proxyBag.Add(proxy); return proxy; })) { try { Parallel.Invoke( () => { //Action A - implement the first action using thread-local proxy //*EXAMPLE* usage of thread-local proxy to execute request //var results = threadLocal.Value.RetrieveMultiple(query); //*TIP* To consolidate results across threads, add to a concurrent collection type //defined in the System.Collections.Concurrent namespace }, () => { //Action B - implement the second action using thread-local proxy //*EXAMPLE* usage of thread-local proxy to execute request //var results = threadLocal.Value.RetrieveMultiple(query); }, () => { //Action C - implement the third action using thread-local proxy //*EXAMPLE* usage of thread-local proxy to execute request //var results = threadLocal.Value.RetrieveMultiple(query); }); } catch (AggregateException ae) { foreach (var ex in ae.InnerExceptions) { //Handle exceptions } } finally { //Dispose of each proxy instance tracked in the ConcurrentBag foreach (var p in proxyBag) { p.Dispose(); } proxyBag = null; //.NET 4.5 provides a .Values collection to track all instances across threads //Requires setting 'trackAllValues' parameter to true //when constructing ThreadLocal //foreach (var p in threadLocal.Values) //{ // p.Dispose(); //} } } Read more about how to use Parallel.Invoke to execute parallel operations here: http://msdn.microsoft.com/en-us/library/dd460705

Remember that any reference types accessed or assigned within the body of each parallel operation should be thread-safe. As noted in each of the patterns, certain thread-safe data structures for parallel programming such as ConcurrentBag can prove helpful .

Hopefully these patterns have provided some practical guidance for combining documented SDK best-practices and lead to a positive impact on general performance within your custom solutions/extensions for Dynamics CRM. I also hope that the scenarios presented with each pattern provide a starting point to identify ways that these patterns can be applied. If you have any questions or suggestions, feel free to post in the comments below.

Austin Jones

Microsoft Premier Field Engineer





Источник: http://blogs.msdn.com/b/crminthefiel...ice-proxy.aspx
__________________
Расскажите о новых и интересных блогах по Microsoft Dynamics, напишите личное сообщение администратору.
 

Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск
Опции просмотра

Ваши права в разделе
Вы не можете создавать новые темы
Вы не можете отвечать в темах
Вы не можете прикреплять вложения
Вы не можете редактировать свои сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.
Быстрый переход

Рейтинг@Mail.ru
Часовой пояс GMT +3, время: 11:42.
Powered by vBulletin® v3.8.5. Перевод: zCarot
Контактная информация, Реклама.