Dynamics Ax RunBaseBatch multithreading


Hi,

Next post will be a little tutorial on how the RunBaseBatch framework can work multithreaded. For example in the SalesFormLetter class on the method run, the following code will be found before the query iteration:

if (this.canMultiThread())
{
    batchHeader = BatchHeader::construct(this.parmCurrentBatch().BatchJobId);
    salesFormLetterEndMultiThread = SalesFormLetterEndMultiThread::newFormLetter(this,
                                                                                 salesParmUpdate.ParmId,
                                                                                 salesParmUpdate.Proforma);
    batchHeader.addRuntimeTask(salesFormLetterEndMultiThread,this.parmCurrentBatch().RecId);
}
Code language: JavaScript (javascript)

The SalesFormLetterEndMultiThread that is being created will be called when all threads connected to that bacth are processed, this will call methods like printJournal and endUpdate. Notice that all the variables that are passed in the construct method are also  defined in the CurrentList macro for packing and unpacking, this is important to keep in mind when writing custom code.

In the iteration itself, another multithread batch task is created for each line.

if (batchHeader)
{
    formLetterMultiThread = FormLetterMultiThread::newFormLetter(this);
    batchHeader.addRuntimeTask(formLetterMultiThread,this.parmCurrentBatch().RecId);
    batchHeader.addDependency(salesFormLetterEndMultiThread,formLetterMultiThread,BatchDependencyStatus::FinishedOrError);
}
Code language: JavaScript (javascript)

So foreach SalesParmTable found an instance of the runtime task FormLetterMultiThread is created, and is a dependency for the SalesFormLetterEndMultiThread to run.

Now let’s create our own simple example.

Start by creating a RunBaseBatch class like you would otherwise do, but make sure that the code witch uses the most load is written in a separate method and called from the run. This method will be called from the threads. (method: updateSalesOrder)

Sales order update class

The canMultiThread method is the same as in the FormLetter class.

protected boolean canMultiThread()
{;
    return this.isInBatch();
}
Code language: JavaScript (javascript)

And the run method could be written like this, analog to the run of the SalesFormLetter class, but without an ending thread.

void run()
{
    int batchCounter    = 0;
    ;
    try
    {
        ttsbegin;

        if(this.canMultiThread())
        {
            batchHeader     = BatchHeader::construct(this.parmCurrentBatch().BatchJobId);
        }

        while(this.queryRun().next())
        {
            salesTable  = this.queryRun().get(TableNum(SalesTable));

            if(batchHeader)
            {
                tSTSalesOrderUpdateMultiThread  = TSTSalesOrderUpdateMultiThread::newFromTSTSalesOrderUpdate(this);
                batchHeader.addRuntimeTask(tSTSalesOrderUpdateMultiThread,this.parmCurrentBatch().RecId);

                batchCounter++;
            }
            else
            {
                this.updateSalesOrder();
            }
        }

        if(batchHeader)
        {
            batchHeader.save();

            info(strfmt("%1 batches created",batchCounter));
        }

        ttscommit;
    }
    catch(Exception::Error)
    {
        ttsabort;
    }
    catch(Exception::Deadlock)
    {
        retry;
    }
}
Code language: JavaScript (javascript)

The second class you need to create is kind of a wrapper class that also extends from RunBaseBatch and will be used to create the subtasks for your batch process. Make sure that the runsImpersonated method returns true.

Sales order update multithread class

Remember that you need to keep an instance of the caller class (TSTSalesOrderUpdate) and you need to pack and unpack it.

class TSTSalesOrderUpdateMultiThread extends RunBaseBatch
{
    BatchHeader         batchHeader;

    TSTSalesOrderUpdate salesOrderUpdate;
    container           packedSalesOrderUpdate;

    #define.CurrentVersion(2)
    #LOCALMACRO.CurrentList
        packedSalesOrderUpdate
    #ENDMACRO
}

public static TSTSalesOrderUpdateMultiThread newFromTSTSalesOrderUpdate(TSTSalesOrderUpdate  _caller)
{
    TSTSalesOrderUpdateMultiThread   instance;
    ;

    instance    = TSTSalesOrderUpdateMultiThread::construct();
    instance.parmSalesOrderUpdate(_caller);

    return instance;
}

public container pack()
{;
    packedSalesOrderUpdate  = salesOrderUpdate.pack();
    return [#CurrentVersion,#CurrentList];
}

public boolean unpack(container _packedClass)
{
    int version     = RunBase::getVersion(_packedClass);

    switch (version)
    {
        case #CurrentVersion:
            [version,#CurrentList] = _packedClass;

            salesOrderUpdate    = TSTSalesOrderUpdate::construct();
            salesOrderUpdate.unpack(packedSalesOrderUpdate);
            return true;
        default :
            return false;
    }

    return false;
}
Code language: PHP (php)

The run method should call the updateSalesOrder on your TSTSalesOrderUpdate class. This means that all the logic is placed in one place, because it should also work when not running in batch. 😉

void run()
{
    ;
    try
    {
        ttsbegin;
        salesOrderUpdate.updateSalesOrder();
        ttscommit;
    }
    catch(Exception::Error)
    {
        ttsabort;
        throw error("error");
    }
    catch(Exception::Deadlock)
    {
        retry;
    }
}
Code language: PHP (php)

In addition you can add an ending multithread class if necessary, like the FormLetterEndMultiThread class.  The maximum number of simultaneous batch thread can be defined on the SysServerConfig form.

The example given is only for educational purposes. (It is somewhat sloppy 🙂 )

4 responses to “Dynamics Ax RunBaseBatch multithreading”

  1. How would one add constraints to the batch tasks? I have 100,000 customers in AX, and many of them are duplicates. I’ve determined which accounts are duplicates and need to be merged into one and I want to write a multithreaded batch job to do it. I want certain tasks to wait until another task has completed.

    For example, I have accounts (1, 3, 4) to merge into 1, and accounts (2,5,8) to merge into 2.

    I merge 3 into 1, then 4 into 1. I don’t want 4 to start merging into 1, until the first part is complete.

    I merge 5 into 2, and 8 into 2, but I don’t want both tasks to run at the same time.

    • Hi Alex,

      Do you want to set these dependencies from code or just manual between batch tasks?

      Kind regards

      • Hi Kevin,

        Does Dynamic AX support multi-connection to MSSQL server??
        It is because even we can implement multi-threading on AX, but the bottle neck will turn to a single connection to SQL server.

        Thanks,
        Jack

        • Hi Jack,

          I don’t think so, the only way to achieve this is to setup multiple AOS instances and cluster them.
          So every AOS has his one single connection and you could spread your multithread tasks over the AOS instances.
          In my experience this gains a lot of performance if the load is spread evenly.

          Kind regards,

          Kevin

Leave a Reply

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