In AX7 we have various new ways to execute a time consuming operation on the user client. We all know the feeling if we execute a very long operation on a program window that operation blocks all the user interface until it is completed. To prevent this, in AX 2012 we used either the progress bar to display the operation progress to user, or some asynchronous execution possibilities like the Thread API or xApplication::runasync() method to run the operation on the background.
In AX7 optimizing our time consuming operations is extra important because we are running them on a web based interface which will be blocked, or ran into a timeout if we are running such a long operation. For that reason, optimizing our execution time and leaving the user interface free for the user with asynchronous execution is important and recommended. We already see it is being used in many new forms of AX7, like the new data import window.
Now lets have a look at the different possibilities we have for running time consuming operations in AX7. To test them we create a simple form like this :
Then we create a new class with a static method that holds a time consuming program code in it, in that example a 10 seconds of sleep :
class TestOperation { public static container returnAfteraWhile(container _callerparameters) { str caller = conPeek(_callerparameters, 1); sleep(10000); return(['Operation completed : ' + caller]); } }
First let’s look at the options we can execute our code synchronously. On the first button we call the static method directly without using anything else in the code, this displays the standard AX7 ‘please wait’ message to us and blocks all other user operations :
[Control("Button")] class CallNormal { /// <summary> /// /// </summary> public void clicked() { super(); TestOperation::returnAfteraWhile(['Syncronous operation..']); info('Syncronous operation finished.'); } }
In our second button we write a code to run the same operation using the new SysOperationSandbox class of SysOperation framework, which replaces the old RunbaseBatch framework in AX 2012:
[Control("Button")] class CallSysOperationSandbox { /// <summary> /// /// </summary> public void clicked() { super(); SysOperationSandbox::callStaticMethod(classNum(TestOperation),staticMethodStr(TestOperation,returnAfteraWhile), ['Sandbox Run Sync'], 'Waiting for sandbox operation', 'Operation completed'); } }
This will run the same code displaying user a progress window, also blocking the other parts of the user interface :
To run our program code asynchronously in the background without blocking the user interface, we use the new runasync methods of FormRun and Global classes. In AX2012 for that purpose we have used the xApplication::runasync() method and Thread framework. Those two are now depreciated and replaced by the optimized Form and Global class methods, which utilizes .NET Tasks for multi-threaded execution in AX.
For the ones who are not familiar with asynchronous programming, our asynchronous method call will simply run in the background without interfering with the user interface and run another method called the ‘callback method’ when it is finished. We create our callback method in the root declaration of our form like this :
public void FormCallBackTest(AsyncTaskResult _result) { container result, resultinfolog; str resultstr; ; info('Async operation finished.'); result = _result.getResult(); resultstr = conPeek(result, 1); info(resultstr); resultinfolog = _result.getInfologData(); if(resultinfolog != conNull()) { setPrefix('Extra information'); InfoLogExtension::showMessagesFromContainer(resultinfolog); } }
And call the same method in our class from our third button using new Formrun runasync method :
[Control("Button")] class CallElementRunAsync { /// <summary> /// /// </summary> public void clicked() { super(); info('process is running..'); element.runAsync(classNum(TestOperation), staticMethodStr(TestOperation,returnAfteraWhile),['Element Run Async'], System.Threading.CancellationToken::None, formMethodStr(AsyncTestForm,FormCallBackTest)); } }
This code will run the static class method on the background and call the callback function passing an AsyncTaskResult object to the method. From that object we can check the return value of the async method call (must be a container) and get the infolog result of the async execution.
You can click other buttons in the form and do other things as well without waiting for the code execution to complete, and we receive information messages as soon as the execution is finished.
The runAsync method of formRun is the one optimized for client side execution. There is another runAsync method on Global class which is to be used in program code that runs outside the client, like services. In our fourth button we will use that one instead of Formrun one (for testing purposes, always use formrun one in client side) and will see the difference.
For that, we need to create a callback function in our class. Just simply copy and paste the callback method from the form and change it to run as static and inside the class instead :
public static void ClassCallBackTest(AsyncTaskResult _result) { container result, resultinfolog; str resultstr; ; info('Async operation finished.'); result = _result.getResult(); resultstr = conPeek(result, 1); info(resultstr); resultinfolog = _result.getInfologData(); if(resultinfolog != conNull()) { setPrefix('Extra information'); InfoLogExtension::showMessagesFromContainer(resultinfolog); } }
And in our button we call similar function for Global class :
[Control("Button")] class CallGlobalRunAsync { /// <summary> /// /// </summary> public void clicked() { super(); info('process is running..'); Global::runAsync(classNum(TestOperation), staticMethodStr(TestOperation,returnAfteraWhile), ['Global Run Async'], System.Threading.CancellationToken::None, classNum(TestOperation), staticMethodStr(TestOperation,ClassCallBackTest)); } }
After you run the code you will realize that you do not receive the information message in time although the operation is completed. This is because the asynchronous execution and callback is done outside the form and form handler does not know about it. You need to refresh the form to see the result of the execution in messages after it is finished. In Formrun version you do not need to do it and get the messages as soon as they are placed in the infolog.
You can download the program code used in this example from my GitHub link below :

*This post is locked for comments