Revised in October 2025,

In D365 F&O, we have various new ways to execute time-consuming operations in the web client. We all know the feeling that if we want to execute a time-consuming operation on an AX form, that operation blocks all the user interface until it is completed, showing a white screen or a waiting message. To prevent this, in AX 2012, we used either the progress bar to display the operation progress to the user, or some asynchronous execution possibilities like the Thread API or xApplication::runasync() method to run the operation in the background.

In D365 F&O, optimizing our time-consuming operations is extra important because we are running them on a web-based interface, which will be blocked or run into a timeout if we run 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 D365 F&O, like the new data import form.

Now, let’s have a look at the different possibilities we have for running time-consuming operations in D365 F&O. To test them, we create a simple form like this :

Capture1

Then we create a new class with a static method that holds a time-consuming program code in it, in that example, a 10-second 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 D365 F&O ‘please wait’ message to us and blocks all other user operations :

[Control("Button")]
class CallNormal
{
///
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 SysOperationsSandbox class of the SysOperations framework, which is the more modernized version of the legacy RunbaseBatch framework in AX 2012:

[Control("Button")]
class CallSysOperationSandbox
{
///
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 a progress window, also blocking the other parts of the user interface :

Capture3

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 the Thread framework. Those two are now deprecated and replaced by the optimized Form and Global class methods, and an async execution support in the SysOperation framework, which utilizes .NET Tasks for multi-threaded execution in F&O.

For those unfamiliar with asynchronous programming, our asynchronous method call will run in the background without interfering with the user interface and execute another method, known as 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 the new FormRun runasync method :

[Control("Button")]
class CallElementRunAsync
{
///
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 in 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 the 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 on the 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 a similar function for the Global class :

[Control("Button")]
class CallGlobalRunAsync
{
///
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 are done outside the form, and the form handler does not know about it. You need to refresh the form to see the result of the execution after it is finished. In the Formrun version, you do not need to do it and get the messages as soon as they are placed in the infolog.

There is now also support for direct async execution in the SysOperation framework, the modernized version of the RunBaseBatch framework classes. Usage is as simple as below; you create the skeleton for the sysoperation class logic with data contract, service class, and controller class, and in the constructor of the controller class or directly in the main method, you set the execution mode to async:

//Data Contract class
[DataContract]
class MyAsyncOperationContract
{
    str message;

    [DataMemberAttribute("Message"), SysOperationLabelAttribute("Message Text")]
    public str parmMessage(str _message = message)
    {
        message = _message;
        return message;
    }
}

//Service (to execute) class
class MyAsyncOperationService extends SysOperationServiceBase
{
    public void runOperation(MyAsyncOperationContract _contract)
    {
        str message = _contract.parmMessage();

        info(strFmt("Running async operation. Message: %1", message));

        // Simulate some work
        sleep(2000);

        info("Async operation completed.");
    }
}

//Controller class 1 contructor
class MyAsyncOperationController extends SysOperationServiceController
{
    public static void main(Args _args)
    {
        MyAsyncOperationController controller = MyAsyncOperationController::construct();
        controller.startOperation();
    }

public static MyAsyncOperationController construct()
    {
        MyAsyncOperationController controller;
        controller = new MyAsyncOperationController();        controller.parmExecutionMode(SysOperationExecutionMode::Asynchronous);//Here it is
        return controller;
    }
}

//Controller class 2 main method
class MyAsyncOperationController extends SysOperationServiceController
{
    public static void main(Args _args)
    {
        MyAsyncOperationController controller = new MyAsyncOperationController();
        controller.parmMethodName(identifierStr(runOperation));
        controller.parmClassName(classStr(MyAsyncOperationService));
        controller.parmExecutionMode(SysOperationExecutionMode::Asynchronous); // Here it is
        controller.startOperation();
    }
}

Here you have two options for async execution: the normal asynchronous and ReliableSynchronous. The ReliableAsynchronous option is also a non-blocking async option, but it guarantees the operation will complete because the job is saved in batch tables and will execute even if the user session closes. ReliableAsynchronous automatically shows completion messages in the Infolog, whereas normal Async does not show them until you refresh the screen.

You can download the program code used in this example from my GitHub link below :

AsyncOperations.zip 

11 thoughts on “Synchronous and asynchronous operations in D365 F&O

  1. I want to display progress status when batch process is going. (exp 20% done…) Is there any way to do with SysOperationSandbox?

  2. How we can lock some objects in async operation in AX7, I mean when we have some operation which inserts data to list, so we have to lock inserting operations, but when I try to use this piece of code:

    try
    {
    System.Threading.Monitor::Enter(locker);
    list.addEnd(data);
    }
    finally
    {
    System.Threading.Monitor::Exit(locker);
    }

    I always have empty list

    1. It is not very clear what you want to achieve from the code but Enter and exit methods take System.Object type as first parameter. They may not accept an AX object as a parameter and throw an exception. Then it falls into finally and list.addEnd is not executed.

  3. Hi Tayfun Sertan Yaman,
    I understood the concept explained in the above blog, Can you please explain, what is the difference between the Sysoperation framework Async and Normal Asynchronization(Above example). Which will be best approach to use?
    Regards,
    Nagesha

    1. The Async running is a feature of AX operation service controller class to run AX services asynchronously. The feature even exists in AX 2012.
      With above examples you can run your code directly async without a operation controller class and service.
      Behind the scenes SYSOP framework uses the same async methods of the ‘Global’ class:

      Class : SysOperationFrameworkService

      internal static void runAsyncObject(ClassId _runClassId, MethodName _runInstanceMethod, Object _callbackObject, str _callbackMethodName, container _parameters)
      {
      xGlobal::runAsyncWithObjectCallback(
      classNum(SysOperationFrameworkService),
      staticMethodStr(SysOperationFrameworkService, instanceToStaticMethodCallWrapper),
      [_runClassId, _runInstanceMethod, _parameters],
      _callbackObject,
      _callbackMethodName);
      }

      1. Hi Tayfun Sertan Yaman,
        Thanks for details information.Currently i am working on AX2012 Asynchronization process,Do you have any sample code?. I tried by using xApplication::runasync() but i am facing some issues. Please share if you have sample code or standard class, So i can refer
        Regards,
        Nagesha

  4. Hi,

    Thanks for your post. I am using method 4, the global Asynchronization. But how to pass a value when we call Global::runAsync? I want to pass a record.

    1. The third parameter of the runAsync method is a container of values to pass to the async method call. First parameter is the caller name but you can add extra container values there, such as passing recid of a record.
      If that is not what you need then there is also the possibility of SysGlobalCache.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.