Dynamics AX

Welcome to my Dynamics AX playground!

Home     Articles     Dynamics AX - Trivia     Dynamics AX Search     Mohammed Rasheed     Contact Me     My Dynamics Ax Blog      
Guide to Setup SSRS with
Add Fields to a Table Using Code
Code to view Ledger Transaction Details
AX Mobile - Installation and Configuration
Forecasting in Dynamics AX 2009
Multi Threaded Programming in DAX
OLAP - Installation and Configuration
Send Text Messages (SMS) from DAX
Sending Emails from DAX without Outlook.
Using Data Definition Groups
A View Thing..
All About Maps..
COM Class Wrapper
Create a Role Centre
Create and Post Counting
Outlook 07 Integration
Alerts to Multiple Users
Counting Lines of Code
 

Multi Threaded Programming in Dynamics AX 2009

 

view in PDF (with Pictures)

 

Ever wrote an application in dynamics AX, which processed task/operation in a sequence, even though the tasks/operations are mutually exclusive??

For example..

Consider the following code, which gives a list of Sales order and Purchase orders that that an item is present on:

SalesLine salesLine;

PurchLine purchLine;

itemId                    itemid;

;

Itemid = “myItem”;

While select salesid from salesline group by salesid where salesLine.itemid == itemId

{

   Info(strfmt(“Sales Order = %1”, salesLine.salesId));

}

While select purchid from purchLine group by purchid where purchLine.itemid == itemId

{

   Info(strfmt(“Purchase Order = %1”, purchLine. purchid));

}

...

AX kernel executes the above code in sequence of occurrence, hence, in the example above, the salesOrders are queried and Only After this Task is completed, will the kernel execute the purchLine query.

Would it not make sense to run both the queries simultaneously??? ...... it surely does! So how do we do it....the answer is, Threads!

Threads are an immensely powerful way of improving performance by performing multiple operation as the same time (pseudo parallelism) and take full advantage of the multi core processors commonly available now...

But Remember... “with great power comes great responsibility”.

Threads should to used with extra extra caution. Not only are they considered to be unsecure, but they are also unpredictable!  For example, if I call Thread1.run(); followed by thread2.run();, then there is no guarantee that thread1 will complete its operations before thread2. This would not be much of a problem, if the threads are performing task that are mutually exclusive (for example, one thread is querying the salesLine table, while the other thread is querying the PurchLine table). However, if the task are not mutually exclusive (for example, both the threads are writing to a shared resource), then the programmer explicitly needs to write code to handle these situations. Other platforms use semaphores to handle this problem. Semaphores are not native in dynamics ax, but can be easily built if required.

For more information, google for Race condition in threads or Dinning Philosophers problem.

In other platforms, threads are executed by extending an abstract class or implementing an interface, however in Dynamics AX, threads are objects of the class ‘Thread’.

In the following example, I wrote some quick code to show you how we could query the purchLine Table and the salesLine table at (almost) the same time and then display the results. There is some code for state handling, but it can be greatly improved to fit the needs of a production environment.

Note: There is a good Tutorial on threads in DynamicsAX. Look at the form Tutorial_Threads in the AOT.

Elements in my project:

Form/ mrThreadItemTransactions

This is a simple form, that lets the user select an item from a drop down, and will populate 2 list (sales and purch), based on the result of thread execution.

Class/mrThreadProcessor

Has two methods, both accept a thread as a parameter, the thread holds the itemId that should be queried. Once the methods complete execution, they add the list of order to the thread and return the thread to the caller.

 

 

Code:

Form:

public class FormRun extends ObjectRun

{

 

    Thread      salesThread;

    Thread      purchThread;

 

    container   salesCon;

    container   purchCon;

}

..............

public void init()

{

    super();

 

    if(inventItem.valueStr())

    {

        element.getPurchSales();

        element.setPurchSales();

 

        element.populatePurchList();

        element.populateSalesList();

    }

 

 

}

.............

void getPurchSales()

{

    ExecutePermission perm;

    ;

 

    perm = new ExecutePermission();

    if (!perm)

    {

        return;

    }

 

    perm.assert();

 

    salesThread = new Thread();

    purchThread = new Thread();

 

    salesThread.removeOnComplete(true);

    purchThread.removeOnComplete(true);

 

    purchThread.setInputParm([inventItem.valueStr()]);

    salesThread.setInputParm([inventItem.valueStr()]);

 

    salesThread.run(classnum(mrThreadProcessor),staticmethodstr(mrThreadProcessor,getSales));

    purchThread.run(classnum(mrThreadProcessor),staticmethodstr(mrThreadProcessor,getPurch));

 

     CodeAccessPermission::revertAssert();

 

}

 

.........

void populateSalesList()

{

    int conL;

    ;

    salesList.clear(); // clear list

 

    conL = conLen(salesCon);

 

    while(conL >0)

    {

      salesList.add(conpeek(salesCon,conL));

      conL--;

    }

 

 

}

 

.....

void populatePurchList()

{

    int conL;

    ;

    purchList.clear(); // clear list

 

    conL = conLen(purchCon);

 

    while(conL >0)

    {

      purchList.add(conpeek(purchCon,conL));

      conL--;

    }

 

}

 

...............

void setPurchSales()

{

;

 

    try

    {

        // retry or wait till both the threads are processed

        if(salesThread.status() != 2 && purchThread.status() != 2) // Warning:: possible infinite look, use carefully

            throw Exception::Internal;

// if status = 2, then that indicates that the thread has completed // execution (I believe, 1 indicates that the thread is inProcess)

        salesCon = salesThread.getOutputParm();

        purchCon = purchThread.getOutputParm();

 

        purchThread = null;

        salesThread = null;

    }

    catch(Exception::Internal)

    {

        retry;

    }

 

 

}

 

 

...............

public boolean modified()

{

    boolean ret;

    ;

 

    ret = super();

 

    if(inventItem.valueStr()) // Bad design: I should be check if //the condition holds in the called method, not (only) in the caller

    {

 

        element.getPurchSales();

        element.setPurchSales();

 

        element.populatePurchList();

        element.populateSalesList();

        element.redraw();

    }

 

    return ret;

}

....

 

Classs

 

static void getSales(thread _t)

{

   salesLine        salesLine;

   itemid           itemId;

   container        outContainer;

 

   if(_t.getInputParm())

        itemid = conpeek(_t.getInputParm(),1);

 

   else

   {

        info("item not found");

        return;

   }

 

   while select salesid from salesLine group by salesid where salesLine.ItemId == itemid

   {

        outContainer += salesLine.salesid;

   }

 

   _t.setOutputParm(outContainer);

 

}

 

....

static void getPurch(thread _t)

{

   purchLine        purchLine;

   itemid           itemId;

   container        outContainer;

 

   if(_t.getInputParm())

        itemid = conpeek(_t.getInputParm(),1);

 

   else

   {

        info("item not found");

        return;

   }

 

   while select purchid from purchline group by purchid where purchline.ItemId == itemid

   {

        outContainer += purchline.PurchId;

   }

 

   _t.setOutputParm(outContainer);

 

}

 

Download