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)
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.
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.
4 responses to “Dynamics Ax RunBaseBatch multithreading”
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