Asynchronous Execution
4D supports both synchronous and asynchronous execution modes, allowing developers to choose the best approach based on performance, responsiveness, and workload distribution.
Básicos
Synchronous Execution
Synchronous execution follows a sequential flow, a step-by-step where each instruction must complete before the next one starts. This means the execution thread is blocked until the operation finishes.
Synchronous execution is used when:
- Task execution must follow a strict order.
- Performance impact is minimal (e.g., quick operations).
- Running in a single-threaded context where blocking is acceptable.
- Synchronous execution blocks the UI and is best suited for quick, ordered tasks where blocking is acceptable.
Asynchronous Execution
Asynchronous execution is event-driven and allows tasks other operations to complete. It relies on callbacks, workers, and event handlers to manage execution flow.
Asynchronous execution is used when:
- An operation takes a long time (e.g., waiting for a server response).
- Responsiveness is critical (e.g., UI interactions).
- Performing background tasks, network communication, or parallel processing.
Choosing Between Synchronous and Asynchronous Execution:
| Scenario | Best Approach |
|---|---|
| Quick operations with minimal processing | Synchronous |
| Tasks requiring strict execution order | Synchronous |
| Long-running background tasks | Asynchronous |
| Long-running UI interactions | Asynchronous |
| Short-running UI interactions | Synchronous |
| High-performance, multi-threaded workloads | Asynchronous |
Core principles
4D provides built-in asynchronous execution capabilities through various classes and commands. These allow background task execution, network communication, and large data processing, while waiting other operations to complete without blocking the current process.
The general concept of asynchronous event management in 4D is based on an asynchronous messaging model using workers (processes that listen to events) and callbacks (functions or formulas automatically invoked when an event occurs). Instead of waiting for a result (synchronous mode), you provide a function that will be automatically called when the desired event occurs. Callbacks can be passed as class functions (recommended) or Formula objects.
This model is common to CALL WORKER, CALL FORM, and classes that support aynchronous execution. All these commands/classes start an operation that runs in the background. The statement that launches the operation returns immediately, without waiting for the operation to finish.
Workers
Asynchronous programming relies on a system of workers (worker processes), which allows code to be executed in parallel without blocking the main process. This is particularly useful for long tasks (such as HTTP calls, executing external processes, background processing), while keeping the user interface responsive.
Using worker processes in asynchronous programming is mandatory since "classic" processes automatically terminate their execution when the process method ends, thus using callbacks is not possible. A worker process stays alive and can listen to events.
Event queue (mailbox)
Each worker (or form window for CALL FORM) has its own message queue. CALL WORKER or CALL FORM simply posts a message to this queue. The worker handles messages one by one, in the order they arrive, within its own context. Process variables, current selections, etc. are preserved.
Bidirectional communication via messages
The calling process posts a message then the worker executes it. The worker can in turn post a message (via CALL WORKER or CALL FORM) back to the caller or another worker to notify an event (task completion, data received, error, progress, etc.). This mechanism replaces the classic return of synchronous calls.
Event listening
In event-driven development, it is obvious that some code must be able to listen for incoming events. Events can be generated by the user interface (such as a mouse click on an object or a keyboard key pressed) or by any other interaction such as an http request or the end of another action. For example, when a form is displayed using the DIALOG command, user actions can trigger events that your code can process. A click on a button will trigger the code associated to the button.
In the context of asynchronous execution, the following features place your code in listening mode:
CALL WORKERexecutes the code for which it has been called, then returns to a listening status from where it can be called afterwards.CALL FORMopens a form and makes it listen for incoming messages from the event queue.- a call for a
wait()listens forterminate()orshutdown()in a callback from any other instance.
Event triggering
Events are automatically triggered during the execution flow and passed to your corresponding callbacks. You can force the triggering of events by calling terminate() or shutdown() during a wait().
Callback execution context
When 4D execute one of your callbacks, it does so in the context of the current process (worker), i.e. if your object is instantiated inside a form, the callback function will be executed in the context of that same form.
For callbacks to work properly in fully asynchronous mode, the operation should generally be launched from a worker (via CALL WORKER). If launched from a process handling UI, some callbacks may not be called until the UI is listening events.
Releasing an asynchronous object
In 4D, all objects are released when no more references to them exist in memory. This typically occurs at the end of a method execution for local variables.
For asynchronous classes, an extra reference is always maintained by 4D in the process that instantiated the object. This reference is only released when the operation is finished, i.e. after the onTerminate event is triggered. This automatic referencing allows your object to survive even if you don't have referenced it specifically in a variable.
If you want to "force" the release of an object at any moment, use a .shutdown() or terminate() function; it triggers the onTerminate` event ànd thus releases the object.
Examples illustrating the common concept
| Feature | Async Launch | Callback / Event Handling |
|---|---|---|
| CALL WORKER | CALL WORKER("wk"; "MyMethod"; $params) | MyMethod is called with $params |
| CALL FORM | CALL FORM($win; "MyMethod"; $params) | MyMethod is called with $params |
| 4D.SystemWorker | 4D.SystemWorker.new(cmd; $options) | Callbacks: onData, onResponse, onError, onTerminate |
Asynchronous programming with 4D classes
Several 4D classes support asynchronous processing:
HTTPRequest– Handles asynchronous HTTP requests and responses.SystemWorker– Executes external processes asynchronously.TCPConnection– Manages TCP client connections with event-driven callbacks.TCPListener– Manages TCP server connections.UDPSocket– Sends and receives UDP packets.WebSocket– Manages WebSocket client connections.WebSocketServer– Manages WebSocket server connections.
All these classes follow the same rules regarding asynchronous execution. Their constructor accepts an options parameter that is used to configure your asynchronous object. It is recommended that the options object is a user class instance which has callback functions. For example, you can create an onResponse() function in the class, it will be automatically called asychronously when a reponse event is fired.
We recommend the following sequence:
- You create the user class where you declare callback functions, for example a
cs.ParamswithonError()andonResponse()functions. - You instantiate the user class (in our example using
cs.Params.new()) that will configure your asynchronous object. - You call the constructor of the 4D class (for example
4D.SystemWorker.new()) and pass the options object as parameter. It starts the operations passed immediately without delay.
Here is a full example of implementation of an options object based upon a user class:
// asynchronous code creation
var $options:=cs.Params.new(10) //see cs.Params class code below
var $systemworker:=4D.SystemWorker.new("/bin/ls -l /Users ";$options)
// "Params" class
Class constructor ($timeout : Real)
This.dataType:="text"
This.data:=""
This.dataError:=""
This.timeout:=$timeout
Function onResponse($systemWorker : Object)
This._createFile("onResponse"; $systemWorker.response)
Function onData($systemWorker : Object; $info : Object)
This.data+=$info.data
This._createFile("onData";this.data)
Function onDataError($systemWorker : Object; $info : Object)
This.dataError+=$info.data
This._createFile("onDataError";this.dataError)
Function onTerminate($systemWorker : Object)
var $textBody : Text
$textBody:="Response: "+$systemWorker.response
$textBody+="ResponseError: "+$systemWorker.responseError
This._createFile("onTerminate"; $textBody)
Function _createFile($title : Text; $textBody : Text)
TEXT TO DOCUMENT(Get 4D folder(Current resources folder)+$title+".txt"; $textBody)
Note that onResponse, onData, onDataError, and onTerminate are functions supported by 4D.SystemWorker.
Once the user class is instantiated; 4D is put in event listening mode, in which case 4D can trigger an event that calls the corresponding function in the user class.
In some cases, you might want to use formulas as property values instead of class functions. Although it is not the best practice, a syntax such as the following is supported:
var $options.onResponse:=Formula(myMethod)
Synchronous execution in asynchronous code
Even when using modern, asynchronous code, you may need to introduce a degree of synchronous execution. For example, you may want a function to wait for a certain amount of time to get a result. It could the case with guaranteed fast network connections or system workers. Then, you can enforce synchronous execution using the wait() function.
The .wait() function pauses execution of the current process and puts 4D in event listening mode. Keep in mind that it will trigger events received from any sources, not only from the object on which the wait() function was called.
The wait() function returns when the onTerminate event has been triggered on the object, or when the provided timeout (if any) has expired. Consequently, you can explicitly exit from a .wait() by calling shutdown() or terminate() from within a callback. Otherwise, the .wait() is exited when the current operation ends.
Exemplo:
var $options:=cs.Params.new()
var $systemworker:=4D.SystemWorker.new("/bin/ls -l /Users ";$options)
$systemworker.wait(0.5) // Waits for up to 0.5 seconds for get file info
Veja também
Blog post: Launch an external process asynchronously
Asynchronous Call