The subtle art of doing more than one thing at a time is not a common Business Central AL pattern. In this video, I walk through different ways of multitasking with AL. Check out the video:

In this video, Erik demonstrates how to use the StartSession function in AL to run background tasks in Business Central — effectively multitasking. He walks through the entire process: starting a background session, passing parameters to it, handling database transaction boundaries, logging results, and debugging common pitfalls like race conditions.
How Sessions Work in Business Central
Every interaction with Business Central follows a fundamental pattern: a client connects, opens a company, executes logic, returns data, and then the session ends. This applies whether you’re using the web client, an API, or any other entry point. Your AL code runs in the middle of one of these sessions.
Each session gets a unique session ID. You can find yours by navigating to Help and Support in Business Central and scrolling down. However, it’s important to understand that session IDs can be reused over time — they’re unique only while the session is active, not forever.
Starting a Background Session
The key function for multitasking in AL is StartSession. It takes several parameters:
- Session ID (VAR) — returns the ID of the newly created session
- Codeunit ID — the codeunit to execute in the background
- Company Name — which company the session should run in
- Record — a record used to pass parameters to the background codeunit
The background codeunit must have an OnRun trigger. Here’s the codeunit Erik builds, which sleeps for 3 seconds, then modifies a customer record:
codeunit 50100 "The Works"
{
TableNo = "StartSession Parameter";
trigger OnRun()
var
Customer: Record Customer;
Log: Record "Session Log";
begin
Log.Init();
Log.StartTime := CurrentDateTime();
Sleep(3 * 1000);
Customer.FindFirst();
Customer.Address := Rec.CustomerNo + ' ' + Rec.VendorNo;
Customer.Modify();
Log.SessionId := SessionId;
Log.Id := Rec.LogId;
Log.StopTime := CurrentDateTime();
Log.Elapsed := Log.StopTime - Log.StartTime;
Log.Insert();
end;
}
Passing Parameters via a Temporary Table
The fourth parameter of StartSession accepts a record, but you should think of it as a way to pass parameters rather than actual database records. Erik creates a temporary table specifically for this purpose:
table 50100 "StartSession Parameter"
{
TableType = Temporary;
fields
{
field(1; CustomerNo; Code[20])
{
}
field(2; VendorNo; Code[20])
{
}
field(3; LogId; Guid)
{
}
}
}
This record is never inserted into the database — it’s purely in-memory. The StartSession function transfers this single in-memory record to the new session. This is actually an exception to the normal rule that temporary table data is isolated per session. The record makes it across because it’s passed explicitly as a parameter, not through the database.
As a side note, Erik points out that no primary key is explicitly defined on this table. In AL (inherited from C/AL behavior), if you don’t specify a key, the first field automatically becomes the primary key. Microsoft kept this behavior for backwards compatibility.
Understanding Transaction Boundaries
A critical thing to understand: the background session runs in a completely separate database transaction. This means:
- If the calling session rolls back, the background session’s changes are not rolled back
- If the background session errors, the calling session is unaffected
- Data committed in the calling session may not yet be visible to the background session when it starts
This separation can actually be useful. For example, if you need to write to a log table even when the main transaction throws an error and rolls back, you could write that log entry from a background session.
However, wrapping StartSession in an if statement is important — if the system cannot start a new session, it will throw an error unless you handle the Boolean return value.
Logging and the Race Condition Pitfall
Erik builds a session log table to track when background tasks start, finish, and how long they take:
table 50101 "Session Log"
{
fields
{
field(1; ID; Guid)
{ }
field(2; SessionId; Integer)
{
}
field(10; Result; Text[100])
{
}
field(20; StartTime; DateTime)
{
}
field(21; StopTime; DateTime)
{
}
field(22; Elapsed; Duration)
{
}
}
keys
{
key(PK; ID)
{ }
}
}
His first approach was to insert the log record in the calling session before starting the background session, then have the background session update it with stop time and elapsed duration. But this immediately hit a race condition: the log record inserted by the calling session hadn’t been committed to the database yet when the background session tried to read it with Log.Get().
The fix? Move all the log writing into the background session itself. Pass only the GUID via the parameter record, and let the background codeunit create the entire log entry:
// In the background codeunit (The Works):
Log.Init();
Log.StartTime := CurrentDateTime();
// ... do work ...
Log.SessionId := SessionId;
Log.Id := Rec.LogId;
Log.StopTime := CurrentDateTime();
Log.Elapsed := Log.StopTime - Log.StartTime;
Log.Insert();
The calling session can then read the log after waiting long enough for the background task to complete:
StartSession(Log.SessionId, Codeunit::"The Works", CompanyName(), Parm);
Sleep(10 * 1000);
Log.Get(Parm.LogId);
Message('Elapsed %1', Log.Elapsed);
The Full Calling Code
Here’s the complete page extension that ties everything together, including a debug mode toggle:
pageextension 50100 CustomerListExt extends "Customer List"
{
trigger OnOpenPage();
var
Vendor: Record Vendor;
Parm: Record "StartSession Parameter";
Log: Record "Session Log";
DebugMode: Boolean;
begin
DebugMode := false;
Parm.CustomerNo := 'hello';
Parm.VendorNo := 'world';
Parm.LogId := CreateGuid();
if DebugMode then
Codeunit.Run(Codeunit::"The Works", Parm)
else begin
StartSession(Log.SessionId, Codeunit::"The Works", CompanyName(), Parm);
Sleep(10 * 1000);
Log.Get(Parm.LogId);
Message('Elapsed %1', Log.Elapsed);
end;
end;
}
The Debug Mode Pattern
Erik highlights a practical pattern he uses in production — a debug flag. When enabled, the codeunit runs synchronously in the foreground using Codeunit.Run(), making it easy to step through with the debugger. When disabled, it runs in the background via StartSession. Debugging background sessions is notoriously difficult, so this toggle is invaluable during development.
Running Code in Another Company
The company parameter in StartSession lets you run the background session in a different company. This is actually more reliable than using Record.ChangeCompany(), which Erik describes as “only surface level deep” — it doesn’t properly handle validation triggers and other deep logic. StartSession opens a full session in the target company, just like a user logging in there directly.
A Note on OnOpenPage
During testing, Erik noticed something important: OnOpenPage can run multiple times (in his case, three times) during a single page load. This is a known Business Central behavior that can catch you off guard, especially when your trigger contains background session logic or sleep calls. His 3-second sleep felt like 30 seconds because the trigger fired three times.
When to Use Background Sessions
Erik outlines two requirements that should both be met before using StartSession:
- The end user doesn’t need the result immediately. Think about how many operations users wait for where they don’t actually care about the return value — they’re just waiting for it to finish. Those are prime candidates for background processing.
- You have a solid error handling strategy. Since errors in the background session won’t surface to the user, you need logging or some other mechanism to detect and handle failures. A more robust approach would write two log entries — one when the task starts and one when it finishes — so you can identify tasks that started but never completed.
Summary
The StartSession function gives AL developers a straightforward way to run code in the background, improving the user experience by not blocking the UI for long-running operations. The key takeaways are: use temporary tables to pass parameters, remember that background sessions run in separate database transactions, watch out for race conditions with uncommitted data, implement a debug mode for development, and always have a logging/error handling strategy. It’s not full-blown parallel processing with semaphores and synchronization primitives, but for many real-world Business Central scenarios — like the SharePoint connector app Erik mentions — it’s exactly what you need.