Mohammed 的个人资料Dynamics AX日志留言簿网络 工具 帮助

日志


8月25日

Multi Threaded Programming in Dynamics AX 2009

Mohammed Rasheed

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.

clip_image001

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.

clip_image002

clip_image003

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);

}

 

my web site - www.dynamic-ax.co.uk

 

评论 (1)

请稍候...
很抱歉,您输入的评论太长。请缩短您的评论。
您没有输入任何内容,请重试。
很抱歉,我们当前无法添加您的评论。请稍后重试。
若要添加评论,需要您的家长授予您相应权限。请求权限
您的家长禁用了评论功能。
很抱歉,我们当前无法删除您的评论。请稍后重试。
您已超过了一天之内允许提供的评论数上限。请在 24 小时后重试。
因为我们的系统表明您可能在向其他用户提供垃圾评论,您的帐户已禁用了评论功能。如果您认为我们错误地禁用了您的帐户,请联系 Windows Live 支持部门
完成下面的安全检查,您提供评论的过程才能完成。
您在安全检查中键入的字符必须与图片或音频中的字符一致。

若要添加评论,请使用您的 Windows Live ID 登录(如果您使用过 Hotmail、Messenger 或 Xbox LIVE,您就拥有 Windows Live ID)。登录


还没有 Windows Live ID 吗?请注册

alessandro发表:
Hi Mohammed,
great example here! It gave me a lot of new ideas.
I'm trying to dig deeper into multithreaded programming in Ax, because it's a realy powerful tool to gain performances, and I'd like to share some experiences.
At the moment I'm facing some surprises, maybe I'm searching in the wrong direction (I'm not finding real documentation about this)...but, have you ever tried to launch threads from server side?
If you create and run a thread from Ax client (all examples are from forms) everything seems to go well, but if you try to instance and execute a thread from a procedure running server side, threads don't run.
Am I missing something? As usually server machine have multiple processors and multi-core architectures, I think it would be reasonable to write multi threaded procedures that run server-side, to take advantage from that, wouldn't it?
Another thing that is not clear to me is that Threads seems not to "see" global cache, neither client (from infolog) nor server (appl). Is this true?
Thank you for your time, obviously I can send you my test projects if you like to try

Regards, Alessandro
8 月 20 日

引用通告

此日志的引用通告 URL 是:
http://dynamic-ax.spaces.live.com/blog/cns!13619E6948204DE3!354.trak
引用此项的网络日志