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.