Udostępnij za pośrednictwem


Batch Parallelism in AX – Part - II

 

Individual Task Modeling

Here in this case parallelism is achieved by creating a separate task for each work unit. You model the tasks by creating a task for each work item. You will have 1:1 mapping between task and the work item. This will eliminate the need for pre-allocation. Since each work item is independently handled by a worker thread, work load distribution will be more consistent. This approach eliminates the problem of a number of large work items getting bundled together and eventually dragging the batch response time.

However if you are trying to process a huge number of work items, you will end up with huge number of batch tasks. The overhead of batch framework to maintain this ridiculous number of tasks will be very high. Batch framework needs to check several conditions, dependencies and constraints each time a set of tasks is completed and a new set of tasks needs to be picked up for execution from ready state.

Continuing the same example of invoicing a bunch of sales orders using this technique:

Note: The code used in this is only an example. Do NOT use it for your Sales Order Posting needs. AX 2012 default Sales Order Posting form uses a much more sophisticated and feature rich way of handling this parallelism.

DemoBatchIndividualTasks:

public class DemoBatchIndividualTasks extends RunBaseBatch
{
str 20 SalesOrder;
#define.CurrentVersion(1)
#localmacro.CurrentList
SalesOrder
#endmacro
}

public void new()
{
super();
}

public container pack()
{
return [#CurrentVersion, #CurrentList];
}

private void parmSalesOrder(str _SalesOrder)
{
SalesOrder= _SalesOrder;
}
//Single Work item per thread
void run()
{
SalesTable salesTable;
SalesFormLetter formletter;
Map SalesMap;

select * from salesTable where salesTable.salesId == SalesOrder
&& salesTable.documentStatus == DocumentStatus::none;
if (salesTable)
{
formletter = SalesFormLetter::construct(DocumentStatus::Invoice);
formletter.getLast();
formletter.resetParmListCommonCS();
formletter.allowEmptyTable(formletter.initAllowEmptyTable(true));
SalesMap = new Map(Types::Int64,Types::Record);
SalesMap.insert(salesTable.recid,salesTable);
formletter.parmDataSourceRecordsPacked(SalesMap.pack());
formletter.createParmUpdateFromParmUpdateRecord(SalesFormletterParmData::initSalesParmUpdateFormletter(DocumentStatus::Invoice, FormLetter.pack()));
formletter.showQueryForm(false);
formletter.initLinesQuery();
formletter.update(salesTable, systemDateGet(), SalesUpdate::All, AccountOrder::None, false, false);
}
}

public boolean unpack(container packedClass)
{
Version version = RunBase::getVersion(packedClass);
switch(version)
{
case #CurrentVersion:
[version,#CurrentList] = packedClass;
break;
default:
return false;
}
return true;
}

public static DemoBatchIndividualTasks construct(str _SalesOrder)
{
DemoBatchIndividualTasks c;
c = new DemoBatchIndividualTasks();
c.parmSalesOrder(_SalesOrder);
return c;
}

Job to Schedule the above batch:

static void scheduleDemoIndividualTasksJob(Args _args)
{
BatchHeader batchHeader;
DemoBatchIndividualTasks demoBatchIndividualTasks;
BatchInfo batchInfo;
SalesTable salesTable;

    ttsBegin;
select count(RecId) from salesTable where salesTable.salesId >= ‘SO-00400001’&& salesTable.salesId <= 'SO-00500000'
&& salesTable.documentStatus == DocumentStatus::none;
if (salesTable.recid > 0)
{
batchHeader = BatchHeader::construct();
batchHeader.parmCaption(strFmt('Batch job for demoBatchIndividualTasks -Invoice SalesOrders %1 thru %2', ‘SO-00400001’, 'SO-00500000'));
while select * from salesTable where salesTable.salesId >= ‘SO-00400001’&& salesTable.salesId <= 'SO-00500000'
&& salesTable.documentStatus == DocumentStatus::none
{
/* Each task is created to process a single work item (in this case a single sales Order)*/
demoBatchIndividualTasks = DemoBatchIndividualTasks::construct( salesTable.salesid);
batchInfo = demoBatchIndividualTasks.batchInfo();
BatchInfo.parmCaption('Invoicing : '+salesTable.salesid);

                batchHeader.addTask(demoBatchIndividualTasks);
}
batchHeader.save();
}
ttsCommit;
info('Done');
}

Assuming I am trying to process 100,000 work items

#Tasks Created

#Batch Threads (In my test server)

#Parallel Tasks that can be executed in parallel at anytime

100,000

10

10

Once the first 10 task complete, the batch framework will load the next 10 task and execute them and so on, in this case it may load 10,000 or more times over all.

Comments

  • Anonymous
    September 25, 2012
    Hi, Really interesting you article.   I did a similar batch using multiple tasks, like you do.However , I can´t pass a parameter (a single int o str value) to the tasks in anyway: I try to pass an integer from 1 to 10 , to 10 tasks, but the tasks always obtain a "0" value for that parameter.I'm using Ax 2009.Do you know what could be the solution?Thanks in advance
  • Anonymous
    July 23, 2013
    Do you have any idea how to post multiple sales/purch orders into one packingslip/invoice?Note: The code used in this is only an example. Do NOT use it for your Sales Order Posting needs. AX 2012 default Sales Order Posting form uses a much more sophisticated and feature rich way of handling this parallelism.