Question Status

Verified
Paul Heisterkamp asked a question on 24 May 2013 2:16 AM

Hi community,

Here is my Scenario in AX 2012 R2 CU1:

I have a form where a calculation data on server, pack the data into container and then load the data on client from the container to the form.

Now I want to change the data loading to an asynchronous process to not block the form while loading the data.

My preferred process is:

1. Call the asynchronous process to load the data from form

2. When the asynchronous process finished, there should be a callback to form

3. The triggered call back should load the data from asynchronous filled container to the form

Has anyone a solution, white paper or example from AX standard on how to do it?

Thanks,

Paul

Reply
Verified Answer
Brandon Wiese responded on 25 May 2013 6:58 AM

Here's the MSDN documentation on the Thread class.

msdn.microsoft.com/.../aa878446(v=ax.50).aspx

Look in the AOT for Forms\Tutorial_Thread and some classes Tutorial_ThreadWork and TutorialThread.

Brandon George posts some excellent details over a series of blogs.

dynamics-ax.blogspot.com/.../dynamics-ax-2009-x-thread-development.html

dynamics-ax.blogspot.com/.../dynamics-ax-threads-more-details.html

Reply
Verified Answer
Brandon Wiese responded on 27 May 2013 5:01 AM

If you're going to try Thread as a solution, I'll throw in some lessons learned.

First, some types of objects don't marshal across threads easily.  I recall needing to send a temporary table and having to pack it into a container and unpack it off-thread.

Here's an example of some code I used from a working AX 4 project.

This method takes calls on the UI thread and makes the Thread.run(..) call.

public client static void submitBackground(SendQueue _sendQueue)
{
Thread thread;
;

new ExecutePermission().assert();
thread = new Thread();
thread.removeOnComplete(true);
thread.setInputParm([_sendQueue.threadSafe()]); // make thread safe
thread.run(classnum(SendBase), staticmethodstr(SendBase,SubmitThread));
CodeAccessPermission::revertAssert();
}

This method accepts that call, unpacks the parameters, and makes the off-thread static call.

public client static void submitThread(Thread _thread)
{
SendQueue sendQueue;
;

[sendQueue] = _thread.getInputParm();
SendBase::submit(sendQueue);
}

This is the method that does the off-thread work, but I've trimmed it to show only the callback into the UI thread.  In my case I'm sending back progress information and updating a progress bar.

public client static void submit(SendQueue _sendQueue)
{
int id;
;


[id] = SendBase::progress([methodstr(SysOperationProgress,New), 0, 4]);
SendBase::progress([methodstr(SysOperationProgress,Update), id, 0, 'Building messages..']);
SendBase::progress([methodstr(SysOperationProgress,Update), id, 1, 'Attaching document..']);
..
}

Finally a snip of my progress callback showing the basic idea.  Note the use of getThisThread() to identify that the code is off-thread, and the call to executeInUIThread() to get back to the UI thread by calling the same method.

public client static container progress(container args) // [SysOperationProgress method, int id, int num, str text]
{
str method;
int id;
int num;
str text;
str owner = classstr(SendBase);
SysGlobalCache cache;
SysOperationProgressEmbedded progress;
;

if (Thread::getThisThread())
{
return Thread::executeInUIThread(classnum(SendBase), staticmethodstr(SendBase,Progress), args);
}
else
{
[method, id, num, text] = args;

cache = infolog.globalCache();

switch (method)
{
case methodstr(SysOperationProgress,New):
{
progress = SendBase::buildProgressForm();
progress.setTotal(num);
progress.start();
for (id = 0; cache.isSet(owner,id); id++) {} // find unused id
cache.set(owner, id, progress);
break;
}
case methodstr(SysOperationProgress,Update):
{

Good luck!

Reply
Suggested Answer
Brandon Wiese responded on 25 May 2013 6:41 AM

There are some built-in examples of doing this in AX. In older versions, the "permissions" form would offload the work of loading its data in another thread and then filling the form after completion, even showing a progress bar while it worked.  Global Search also does some threading to keep the UI thread responsive during long searches.

You want to use the Thread object.  This has always been very lightly documented, but there are some good blogs out there discussing its use though mostly in older versions of AX.  I have used it in some custom scenarios to offload work, and even sending messages back to the UI thread (since you can not reference any UI element or the infolog from a non-UI thread).

I'm going to have some need to use Thread in 2012 R2 myself soon.

Reply
Suggested Answer
Dominic Lee responded on 26 May 2013 10:21 PM

Hi,

You can also consider using the new SysOperation framework in AX2012. Check out how "Asynchronous" and "Reliable Asynchronous" execution mode works in the "Introduction to the SysOperation Framework" whitepaper here. The sample code used in the whitepaper can be found here.

Cheers

kind regards,

Dominic Lee

My blog | PBC

This forum post is my own opinion and does not necessarily reflect the opinion or view of Microsoft, its employees, or other MVPs.

Reply
Suggested Answer
Brandon Wiese responded on 28 May 2013 3:01 PM

Can you not use an AOT table of type TempDb temporary table?  If so, then the table name is a given, but then you want to identify the correct records (as opposed to records possibly inserted by other users at the same time or left over from previous operations).  I would set the Created by transaction to true, and then insert all of your records within a single ttsbegin/ttscommit block, which will guarantee they all share the same transaction Id, which you can send back to the calling form.  This is similar to how SSRS operates with TempDb tables also.

Reply
Suggested Answer
Brandon Wiese responded on 25 May 2013 6:41 AM

There are some built-in examples of doing this in AX. In older versions, the "permissions" form would offload the work of loading its data in another thread and then filling the form after completion, even showing a progress bar while it worked.  Global Search also does some threading to keep the UI thread responsive during long searches.

You want to use the Thread object.  This has always been very lightly documented, but there are some good blogs out there discussing its use though mostly in older versions of AX.  I have used it in some custom scenarios to offload work, and even sending messages back to the UI thread (since you can not reference any UI element or the infolog from a non-UI thread).

I'm going to have some need to use Thread in 2012 R2 myself soon.

Reply
Verified Answer
Brandon Wiese responded on 25 May 2013 6:58 AM

Here's the MSDN documentation on the Thread class.

msdn.microsoft.com/.../aa878446(v=ax.50).aspx

Look in the AOT for Forms\Tutorial_Thread and some classes Tutorial_ThreadWork and TutorialThread.

Brandon George posts some excellent details over a series of blogs.

dynamics-ax.blogspot.com/.../dynamics-ax-2009-x-thread-development.html

dynamics-ax.blogspot.com/.../dynamics-ax-threads-more-details.html

Reply
Suggested Answer
Dominic Lee responded on 26 May 2013 10:21 PM

Hi,

You can also consider using the new SysOperation framework in AX2012. Check out how "Asynchronous" and "Reliable Asynchronous" execution mode works in the "Introduction to the SysOperation Framework" whitepaper here. The sample code used in the whitepaper can be found here.

Cheers

kind regards,

Dominic Lee

My blog | PBC

This forum post is my own opinion and does not necessarily reflect the opinion or view of Microsoft, its employees, or other MVPs.

Reply
Paul Heisterkamp responded on 26 May 2013 11:57 PM

Hey,

thank you all for your hints.

@Brandon Wiese: I will check the usage of Thread Class in combination with setTimeOut(..) method. And a will keep this thread updated with my solution...

@DOLEE: Yes I had found the SysOperation Framework, but I found no way to perform a callback to form...

Regards,

Paul

Reply
Dominic Lee responded on 27 May 2013 1:05 AM

Hi Paul,

The example in the whitepaper did the following instead(not exactly a callback):

1) Assign a unique GUID to the call.

2) Start the async op, when it's done, it'll create record(s) in a resultTable.

3) The form will periodically polling the resultTable with the GUID to see whether the process has finished. (Using setTimeOut method. =] )

You can look at the SysOpFindPrimesForm (in the sample code for the whitepaper) to see how it is implemented.

Cheers

kind regards,

Dominic Lee

My blog | PBC

This forum post is my own opinion and does not necessarily reflect the opinion or view of Microsoft, its employees, or other MVPs.

Reply
Verified Answer
Brandon Wiese responded on 27 May 2013 5:01 AM

If you're going to try Thread as a solution, I'll throw in some lessons learned.

First, some types of objects don't marshal across threads easily.  I recall needing to send a temporary table and having to pack it into a container and unpack it off-thread.

Here's an example of some code I used from a working AX 4 project.

This method takes calls on the UI thread and makes the Thread.run(..) call.

public client static void submitBackground(SendQueue _sendQueue)
{
Thread thread;
;

new ExecutePermission().assert();
thread = new Thread();
thread.removeOnComplete(true);
thread.setInputParm([_sendQueue.threadSafe()]); // make thread safe
thread.run(classnum(SendBase), staticmethodstr(SendBase,SubmitThread));
CodeAccessPermission::revertAssert();
}

This method accepts that call, unpacks the parameters, and makes the off-thread static call.

public client static void submitThread(Thread _thread)
{
SendQueue sendQueue;
;

[sendQueue] = _thread.getInputParm();
SendBase::submit(sendQueue);
}

This is the method that does the off-thread work, but I've trimmed it to show only the callback into the UI thread.  In my case I'm sending back progress information and updating a progress bar.

public client static void submit(SendQueue _sendQueue)
{
int id;
;


[id] = SendBase::progress([methodstr(SysOperationProgress,New), 0, 4]);
SendBase::progress([methodstr(SysOperationProgress,Update), id, 0, 'Building messages..']);
SendBase::progress([methodstr(SysOperationProgress,Update), id, 1, 'Attaching document..']);
..
}

Finally a snip of my progress callback showing the basic idea.  Note the use of getThisThread() to identify that the code is off-thread, and the call to executeInUIThread() to get back to the UI thread by calling the same method.

public client static container progress(container args) // [SysOperationProgress method, int id, int num, str text]
{
str method;
int id;
int num;
str text;
str owner = classstr(SendBase);
SysGlobalCache cache;
SysOperationProgressEmbedded progress;
;

if (Thread::getThisThread())
{
return Thread::executeInUIThread(classnum(SendBase), staticmethodstr(SendBase,Progress), args);
}
else
{
[method, id, num, text] = args;

cache = infolog.globalCache();

switch (method)
{
case methodstr(SysOperationProgress,New):
{
progress = SendBase::buildProgressForm();
progress.setTotal(num);
progress.start();
for (id = 0; cache.isSet(owner,id); id++) {} // find unused id
cache.set(owner, id, progress);
break;
}
case methodstr(SysOperationProgress,Update):
{

Good luck!

Reply
Paul Heisterkamp responded on 28 May 2013 8:42 AM

Hey Brandon,

First of all thank you for your examples, they helped me allot!

Your solution worked for my scenario to calculate a container in thread and send the result back to UI.

So first step resolved...Great!

But my second scenario is to fill a temp table in thread. The solution of sending contend of the temp table via container from thread to UI is not applicable because there are too many records in the temp table. So I have to pass the instance of temp table to the thread.

Do you know a way to pass the instance of a temp table? Maybe via temptable.getPhysicalTableName()?

The only way I found to fill the instance is direct SQL on from getPhysicalTableName() returned table.

Paul

Reply
Suggested Answer
Brandon Wiese responded on 28 May 2013 3:01 PM

Can you not use an AOT table of type TempDb temporary table?  If so, then the table name is a given, but then you want to identify the correct records (as opposed to records possibly inserted by other users at the same time or left over from previous operations).  I would set the Created by transaction to true, and then insert all of your records within a single ttsbegin/ttscommit block, which will guarantee they all share the same transaction Id, which you can send back to the calling form.  This is similar to how SSRS operates with TempDb tables also.

Reply
Paul Heisterkamp responded on 28 May 2013 11:56 PM

Yes a have a AOT table of type TempDb but Created by Transaction will not do the trick...

The following code show up the possibility of linking temptables by using linkPhysicalTableInstance() but in the thread a do not have the instance where I want to link to. So I need the possibility of linking the temptable to a PhysicalTableName or a similar solution :-)

static void tempDbJob(Args _args)

{

   TmpInventAge    tmpInventAge;

   str             physicalTableName;

   TmpInventAge    tmpInventAge2;

   str             physicalTableName2;

   TmpInventAge    tmpInventAge3;

   str             physicalTableName3;

   select firstOnly tmpInventAge;

   physicalTableName = tmpInventAge.getPhysicalTableName();

   select firstOnly tmpInventAge2;

   physicalTableName2 = tmpInventAge2.getPhysicalTableName();

   tmpInventAge3.linkPhysicalTableInstance(tmpInventAge);

   select firstOnly tmpInventAge3;

   physicalTableName3 = tmpInventAge3.getPhysicalTableName();

}

Reply
Paul Heisterkamp responded on 3 Jun 2013 11:25 PM

Hey all,

Here is my summary.

1. If you want to calculate a container in a dedicated thread, this will work with Brandon’s solution.

2. If you want to fill a temp table in a dedicated thread, this is not possible. The only way to do this is to pack the records in the thread and send them back to UI via container. This solution is not applicable if you handle with thousands of records...

Cheers,

Paul

Reply
Brandon Wiese responded on 4 Jun 2013 2:52 PM

Sorry to hear that the temp table isn't working out so well.

You tried a tempdb table and not an in-memory table?

What went wrong?  Not see any records?  Get any errors?

Did you create your own UserConnection() and assign it to your table buffer, doing your own connection based ttsbegin() and ttscommit() around your data operations?

Reply