Multithreading in Business central AL

Hi

This post expose how to run multithreading operation in AL using subsession mecanism. I’ll provide codeunit to ease the usage with a nice UI.

If you ever need to run an heavy process in Business Central that could be splitted in multiple parrallel task (with codeunit and optionaly record input) then this could definitively interest you. Each codeunit can also be run for different company.

Here how it look with the gui enabled on foreground :

Child Session Mecanism

I recommand you to have a look in this “startsession” function. This exist since a while (even in older Nav) but never seen it used by anyone.
https://learn.microsoft.com/en-us/dynamics-nav/startsession-function–sessions-

Basicaly this function allow you to launch a codeunit OnRun() in a separated child-session with current user privilege, and continue the current code independently.
This is exactly like multi-threading, but Microsoft doesn’t provide any function to retrieve the child-session status, so you’ll have to dig yourself in the logs.
Here why you’d prefer to use existing code to do this and eventually have the above cool window displaying the threads progess 😎

How to use it

Cod62732.MultiThreadingHelper.al

AddThread(…)
            Add a thread to the list to be executed concurrently
RunThreads(WaitForCompletion: Boolean; ShowGUI: Boolean; TimeOutMS: Integer) ErrorOccured: Boolean
            Launch execution of all requested threads
            WaitForCompletion : indicate to wait for all thread completion to continue the code
            ShowGUI : only if WaitforCompletion, display a window of thread status and progression
            TimeOutMS : milliseconds time out that will stop waiting and continue the code
            ErrorOccured : true if an error ocurred in any thread of timeout trigerred
Use the CLEAR() function on this codeunit if you’d like to use it multiple time in a row.

Exemple :

var
    BankAccount: Record "Bank Account";
    MultiThreadingCdu: Codeunit "MultiThreading Helper";
begin
// Create one thread for every company bank account 
if BankAccount.FindSet() then 
repeat 
    MultiThreadingCdu.AddThread(codeunit::"Import Bank Files", BankAccount); 
until BankAccount.Next() = 0; 

// Run all threads and wait for completion with GUI, set time out of 1h 
if not MultiThreadingCdu.RunThreads(true, true, 3600000) then 
    error('An error occured in one of the threads');

Limitations

Error handling

Because sessions are running independantly, when an error occure in one it only rollback the running session and not the other finished sessions because things are already commited.

Also, the only way I found to retrieve a child session error is in the Session Event Log. Microsoft does put a Comment in the “LogOff” event in case of error with it description.

The error description is limited to 132 caracters long and do not mention the callstack.

Number of concurrent thread

I do not found any limitation about the number of concurent session/child session for On premise installtion.

However, there is limitation for Cloud instance as Microsoft protect themself for overload. There is a limit of 100 child sessions and only 5 of them are executed concurently while the other are queued.

Read more details here :
https://learn.microsoft.com/en-us/dynamics365/business-central/dev-itpro/administration/operational-limits-online#Task

Concurrent table access

Editing records in concurrent session induce two risks :

  1. Editing outdated data if another session had finish and commited in the mean time leading to a “the record is not up to date” kind of error.
    However this could be solve using the “SELECTLASTVERSION” function to clear the session cache right before editing record to unsure the data is up to date with latest comit.
    https://learn.microsoft.com/en-us/dynamics-nav/selectlatestversion-function–database-
  2. Editing record collision : editing a record wich have been modified and commited by another session will lead to a lock request, awaiting the other session to finish or commit. Limit the impact of locking time you can use thoses tricks
    • Do not use findset with [ForUpdate] argument if you’re not editing the record
    • Alway use “if not Rec.Isempty” before using modifyall or deleteall
    • Do not use Locktable
    • Avoid using long tree function/base app function as much as possible
    • Move writing transaction at the end of your code, and reading part at the begining
  3. Deadlock risk : only apply if you run several different codeunit. It is not recommanded to use this with different codeunit with having long tree execution including standard function because Microsoft Base App does contain a lot of locktable() wich increase the deadlock risk.
    If you run the same codeunit with different input record they will just run sequentically whenever there is a locktable or a findset(true) without deadlock risk (bellow ugly schema show same codeunit concurrent run with an transaction starting in the middle)

child session chronology using the same codeunit

Leave a Reply

Your email address will not be published. Required fields are marked *