Adding runtime tasks to a business operation framework service

February 27, 2012 at 14:34
filed under Dynamics AX
Tagged , , , , , ,

Hi all!

When I was at the technical conference in Nice last year, I attended the session about performance (BRK224), and during a demo, something interesting was shown: how to add one BOF service as a runtime task to another BOF service in batch.
You can watch the session recording here, the code is shown about 44 minutes, 50 seconds in.

Background: Runtime tasks in Dynamics AX 2009

If you are unfamiliar with runtime tasks and how to use them, read my post about batch multithreading for an explanation. The basic principle stays the same, but in the example below, we will be using the Business Operation Framework (aka BOF, aka SysOperation) instead of RunBaseBatch.

Runtime tasks in Dynamics AX 2012 using BOF

The first thing you’ll need to do, is create 2 BOF services, one that will create the runtime tasks, and one that will represent the runtime task. If you don’t know how to do that, read this post: SysOperation Introduction. You can download the xpo file for the following example below.

KlForSubTaskService: the runtime task

This service will be our runtime task. It’s a regular BOF service, nothing special about it. In my example, the service operation contains some testing code so we can see if our service is working:

class KlForSubTaskService
{
}

[SysEntryPointAttribute]
public void runSubTask(KlForSubTaskDataContract _klForSubTaskDataContract)
{
    info(strFmt("%1", _klForSubTaskDataContract.parmTheInt()));
}

The data contract for this service has one member:

[DataContractAttribute]
class KlForSubTaskDataContract
{
    Integer theInt;
}

[DataMemberAttribute]
public Integer parmTheInt(Integer _theInt = theInt)
{
    theInt = _theInt;

    return theInt;
}

KlForBatchJobService: Batch header service

This is the service that will create the runtime tasks. Starting for a regular BOF service, make sure that the service class extends SysOperationServiceBase. This enables us to call the isExecutingInBatch() method later on.

class KlForBatchJobService extends SysOperationServiceBase
{
}

The service operation that creates the runtime tasks looks like this:

[SysEntryPointAttribute]
public void createTasks()
{
    BatchHeader                    batchHeader;
    SysOperationServiceController  controller;
    KlForSubTaskDataContract       dataContract;

    int                             i;
    ;

    // for demo purposes, loop 5 times to add 5 runtime tasks
    for(i=1;i<=5;i++)
    {
        if(this.isExecutingInBatch() && !batchHeader)
        {
            // if no batchheader has been set yet, get it
            batchHeader = this.getCurrentBatchHeader();
        }

        // create new controller for the runtime task
        controller = new SysOperationServiceController(classStr(KlForSubTaskService), methodStr(KlForSubTaskService, runSubTask));

        // get the data contract
        dataContract = controller.getDataContractObject('_klForSubTaskDataContract');
        // initialize variables for the data contract
        dataContract.parmTheInt(i);

        if(this.isExecutingInBatch())
        {
            // code is running in batch, so add a runtime task
            batchHeader.addRuntimeTask(controller, this.getCurrentBatchTask().RecId);
        }
        else
        {
            // not in batch

            // by default, the tasks will execute in reliable asynchronous mode,
            // but you could override it so it executes synchronous
            //controller.parmExecutionMode(SysOperationExecutionMode::Synchronous);

            // not in batch, just run it
            controller.run();
        }
    }

    if(batchHeader)
    {
        // save the batchheader if it has been created
        batchHeader.save();
    }

}

For this demo, I created a loop that creates 5 runtime tasks.
When you execute this in batch, you can check the batch task history for the batch to see the tasks that have been created.

You can click the Parameters button to verify if the correct values were used to execute the task.

You can download the XPO here.

Links
Another example can be found on Kenny Saelen’s blog: Business Operation Framework and multi-threading

Related posts
AX2012: SysOperation part 1: Data Contracts and Service Operations
AX2012: SysOperation part 2: SysOperationServiceController
AX2012: SysOperation part 3: SysOperationAutomaticUIBuilder
AX2009: Batch multithreading (RunBaseBatch)

10 comments

RSS / trackback

  1. Michael

    Has this actually worked for you? I get the tasks created, but they don’t do anything. I have an info log and nothing shows up. I have added some code in there, and it doesn’t process.

  2. Klaas Deforche

    Hi Michael,

    Sorry you are having problems.
    I will check the code again and let you know.

  3. Brian

    I have gotten this to work, but It seems that two batchhistory entries are created for each sub task, unless I create the sub tasks in synchronous mode – one entry that can be seen from the batchjob form (batchjobhistory button) and another that can be seen from batchhistory in the main menue. Any errors or infos written to the infolog in the sub task are only stored with the entry seen from the main menue.

    Do you know what difference it would make to change your code to:
    // create new controller for the runtime task
    controller = new SysOperationServiceController(classStr(KlForSubTaskService), methodStr(KlForSubTaskService, runSubTask), SysOperationExecutionMode::Synchronous);

  4. Klaas Deforche

    Hi Brain,

    Thanks for your comment. The default execution mode is probably asynchronousReliable, so that might be the reason why two batchhistory entries are created. I’ve never noticed that behavior before. I’m not really sure if it will behave differently if you use synchronous apart from that second entry in the batchhistory table. I think it won’t :). If I have the time, I will do some testing and let you know. Remind me if I forget (I tend to :-)).

  5. AJ

    I agree with Brian that there is strange behaviour if the sub-tasks are set to Reliable Asynchronous. The batch history does indeed end up with two rows but infolog output is only visible in one of them (NOT the one linked to the parent batch job, so not the one you’d normally check in my opinion)

    Changing it to synchronous fixes this and doesn’t seem to cause any side effects. I haven’t dug further but I suspect this is a standard AX bug.

  6. AlexOnDax

    ALSO, if you try and setup dependencies with the sub-tasks, if they’re not Synchronous, they won’t work. Not sure why yet, but was racking my brain on why my threading framework dependencies weren’t working.

  7. AJ

    I haven’t tried setting dependencies but I have used this method in a couple of places now on a live project and it works really well.

    For example, we have a service that runs in batch every few minutes to do some processing. The problem was that if the actual processing took more than a few minutes then it would start to delay the subsequent executions. I’ve now changed it so that the main batch task (which still runs every few minutes) just spawns off sub-tasks to do the actual processing. It therefore completes almost immediately and is always ready on time fo rthe next iteration. PLUS if there are multiple sub-tasks they will now be picked up by multiple threads/batch servers, increasing performance.

    In general I’m starting to view the spawning of runtime batch tasks as a potential solution to any long-running task that doesn’t need to be synchronous. It takes time for the new possibilities to start to make logical sense but I am doing this sometimes now for user-generated actions as well, without giving them the complication of deciding if it should be run in batch or not.

    Unfortunately there are some bugs in the reliable asynchronous mode that stop it reliably showing the results infolog which is annoying, but some people have posted fixes for these.

  8. Klaas Deforche

    Thanks for your comment AJ. Good to hear that you have implemented this succesfully. We do this also for long running batches where performance can be an issue.

    Like you say, it’s a great way to make use of your infrastructure and make a solutions that is scalable.

    About the dependencies, I never use them because we had some trouble with this on AX2009 where the dependencies were not respected. Maybe that’s fixed now but i’m not taking any chances when doing new customizations.

    By the way there is a great series about batch parallelism on the performance team blog where they explore different ways to implement this: http://blogs.msdn.com/b/axperf/archive/2012/02/24/batch-parallelism-in-ax-part-i.aspx (there are other parts as well).

  9. AJ

    Thanks I’ve seen those pages too, and in fact I used the info to implement something similar to the “top picking” version for processing of some custom “journals”. It also included an “ordered processing” requirement where some (but not all) journals are dependent on earlier ones processing first.

    It was a nice problem to solve and I use a staging table as suggested in that link along with various statuses (and blocking information to deal with the ordered processing). Each thread picks off the next journal as the thread becomes free, skipping blocked ones. When a “blocking” one is processed it will release all dependent journals making them available for subsequent pickups.

    My solution was a bit “brute force” but it works well and as each journal takes several seconds to post it’s OK to lose a few ms here and there in management overhead. Watching multiple threads power through the journals is very satisfying when you’re used to the “old” AX ways where such things were much harder :)

  10. sashi

    Hi,
    I have an issue with the runtime task, it always run in the empty batch group rather the parent batch header batch group,
    could you please tell me how could it run to the specific batch group (main header batch batch group)

respond