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);
}
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);
}
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)
The canMultiThread method is the same as in the FormLetter class.
protected boolean canMultiThread()
{;
    return this.isInBatch();
}
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;
    }
}
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.
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;
}
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;
    }
}
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.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.