|
| 1 | +--- |
| 2 | +id: async |
| 3 | +title: Asynchronous Execution |
| 4 | +--- |
| 5 | + |
| 6 | +4D supports both **synchronous** and **asynchronous** execution modes, allowing developers to choose the best approach based on performance, responsiveness, and workload distribution. |
| 7 | + |
| 8 | +## Basics |
| 9 | + |
| 10 | +#### Synchronous Execution |
| 11 | + |
| 12 | +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. |
| 13 | + |
| 14 | +Synchronous execution is used when: |
| 15 | +- Task execution must follow a strict order. |
| 16 | +- Performance impact is minimal (e.g., quick operations). |
| 17 | +- Running in a single-threaded context where blocking is acceptable. |
| 18 | +- Synchronous execution blocks the UI and is best suited for quick, ordered tasks where blocking is acceptable. |
| 19 | + |
| 20 | +#### Asynchronous Execution |
| 21 | + |
| 22 | +Asynchronous execution is **event-driven** and allows tasks other operations to complete. It relies on **callbacks**, **workers**, and **event handlers** to manage execution flow. |
| 23 | + |
| 24 | +Asynchronous execution is used when: |
| 25 | +- An operation takes a long time (e.g., waiting for a server response). |
| 26 | +- Responsiveness is critical (e.g., UI interactions). |
| 27 | +- Performing background tasks, network communication, or parallel processing. |
| 28 | + |
| 29 | +Choosing Between Synchronous and Asynchronous Execution: |
| 30 | + |
| 31 | +| Scenario | Best Approach | |
| 32 | +|----------|---------------| |
| 33 | +| Quick operations with minimal processing | **Synchronous** | |
| 34 | +| Tasks requiring strict execution order | **Synchronous** | |
| 35 | +| Long-running background tasks | **Asynchronous** | |
| 36 | +| Long-running UI interactions | **Asynchronous** | |
| 37 | +| Short-running UI interactions | **Synchronous** | |
| 38 | +| High-performance, multi-threaded workloads | **Asynchronous** | |
| 39 | + |
| 40 | +## Core principles |
| 41 | + |
| 42 | +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. |
| 43 | + |
| 44 | +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. |
| 45 | + |
| 46 | +This model is common to [`CALL WORKER`](../commands-legacy/call-worker.md), [`CALL FORM`](../commands-legacy/call-form.md), and [classes that support aynchronous execution](#asynchronous-programming-with-4d-classes). 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. |
| 47 | + |
| 48 | +### Workers |
| 49 | + |
| 50 | +Asynchronous programming relies on a system of [**workers**](../Develop/processes.md#worker-processes) (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. |
| 51 | + |
| 52 | +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**. |
| 53 | + |
| 54 | +### Event queue (mailbox) |
| 55 | + |
| 56 | +Each worker (or form window for [`CALL FORM`](../commands-legacy/call-form.md)) has its own message queue. [`CALL WORKER`](../commands-legacy/call-worker.md) or [`CALL FORM`](../commands-legacy/call-form.md) 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. |
| 57 | + |
| 58 | +### Bidirectional communication via messages |
| 59 | + |
| 60 | +The calling process posts a message then the worker executes it. The worker can in turn post a message (via [`CALL WORKER`](../commands-legacy/call-worker.md) or [`CALL FORM`](../commands-legacy/call-form.md)) 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. |
| 61 | + |
| 62 | +### Event listening |
| 63 | + |
| 64 | +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. |
| 65 | + |
| 66 | +In the context of asynchronous execution, the following features place your code in listening mode: |
| 67 | + |
| 68 | +- [`CALL WORKER`](../commands-legacy/call-worker.md) executes the code for which it has been called, then returns to a listening status from where it can be called afterwards. |
| 69 | +- [`CALL FORM`](../commands-legacy/call-form.md) opens a form and makes it listen for incoming messages from the event queue. |
| 70 | +- a call for a `wait()` listens for `terminate()` or `shutdown()` in a callback from any other instance. |
| 71 | + |
| 72 | +### Event triggering |
| 73 | + |
| 74 | +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()`. |
| 75 | + |
| 76 | + |
| 77 | +### Callback execution context |
| 78 | + |
| 79 | +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. |
| 80 | + |
| 81 | +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. |
| 82 | + |
| 83 | + |
| 84 | + |
| 85 | +### Releasing an asynchronous object |
| 86 | + |
| 87 | +In 4D, all objects are released [when no more references](../Concepts/dt_object.md#resources) to them exist in memory. This typically occurs at the end of a method execution for local variables. |
| 88 | + |
| 89 | +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. |
| 90 | + |
| 91 | +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. |
| 92 | + |
| 93 | +### Examples illustrating the common concept |
| 94 | + |
| 95 | +|Feature|Async Launch|Callback / Event Handling| |
| 96 | +|---|----|---| |
| 97 | +|CALL WORKER|CALL WORKER("wk"; "MyMethod"; $params)|MyMethod is called with $params| |
| 98 | +|CALL FORM|CALL FORM($win; "MyMethod"; $params)|MyMethod is called with $params| |
| 99 | +|4D.SystemWorker|4D.SystemWorker.new(cmd; $options)|Callbacks: onData, onResponse, onError, onTerminate| |
| 100 | + |
| 101 | +## Asynchronous programming with 4D classes |
| 102 | + |
| 103 | +Several 4D classes support asynchronous processing: |
| 104 | + |
| 105 | +- [`HTTPRequest`](../API/HTTPRequestClass.md) – Handles asynchronous HTTP requests and responses. |
| 106 | +- [`SystemWorker`](../API/SystemWorkerClass.md) – Executes external processes asynchronously. |
| 107 | +- [`TCPConnection`](../API/TCPConnectionClass.md) – Manages TCP client connections with event-driven callbacks. |
| 108 | +- [`TCPListener`](../API/TCPListenerClass.md) – Manages TCP server connections. |
| 109 | +- [`UDPSocket`](../API/UDPSocketClass.md) – Sends and receives UDP packets. |
| 110 | +- [`WebSocket`](../API/WebSocketClass.md) – Manages WebSocket client connections. |
| 111 | +- [`WebSocketServer`](../API/WebSocketServerClass.md) – Manages WebSocket server connections. |
| 112 | + |
| 113 | +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](../Concepts/classes.md) 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. |
| 114 | + |
| 115 | +We recommend the following sequence: |
| 116 | + |
| 117 | +1. You create the user class where you declare callback functions, for example a `cs.Params` with `onError()` and `onResponse()` functions. |
| 118 | +2. You instantiate the user class (in our example using `cs.Params.new()`) that will configure your asynchronous object. |
| 119 | +3. 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. |
| 120 | + |
| 121 | +Here is a full example of implementation of an *options* object based upon a user class: |
| 122 | + |
| 123 | +```4d |
| 124 | +// asynchronous code creation |
| 125 | +var $options:=cs.Params.new(10) //see cs.Params class code below |
| 126 | +var $systemworker:=4D.SystemWorker.new("/bin/ls -l /Users ";$options) |
| 127 | +
|
| 128 | +
|
| 129 | +// "Params" class |
| 130 | +
|
| 131 | +Class constructor ($timeout : Real) |
| 132 | + This.dataType:="text" |
| 133 | + This.data:="" |
| 134 | + This.dataError:="" |
| 135 | + This.timeout:=$timeout |
| 136 | +
|
| 137 | +Function onResponse($systemWorker : Object) |
| 138 | + This._createFile("onResponse"; $systemWorker.response) |
| 139 | +
|
| 140 | +Function onData($systemWorker : Object; $info : Object) |
| 141 | + This.data+=$info.data |
| 142 | + This._createFile("onData";this.data) |
| 143 | +
|
| 144 | +Function onDataError($systemWorker : Object; $info : Object) |
| 145 | + This.dataError+=$info.data |
| 146 | + This._createFile("onDataError";this.dataError) |
| 147 | +
|
| 148 | +Function onTerminate($systemWorker : Object) |
| 149 | + var $textBody : Text |
| 150 | + $textBody:="Response: "+$systemWorker.response |
| 151 | + $textBody+="ResponseError: "+$systemWorker.responseError |
| 152 | + This._createFile("onTerminate"; $textBody) |
| 153 | +
|
| 154 | +Function _createFile($title : Text; $textBody : Text) |
| 155 | + TEXT TO DOCUMENT(Get 4D folder(Current resources folder)+$title+".txt"; $textBody) |
| 156 | +
|
| 157 | +``` |
| 158 | + |
| 159 | +Note that `onResponse`, `onData`, `onDataError`, and `onTerminate` are functions supported by [`4D.SystemWorker`](../API/SystemWorkerClass.md). |
| 160 | + |
| 161 | +Once the user class is instantiated; 4D is put in [event listening](#event-listening) mode, in which case 4D can [trigger an event](#event-triggering) that calls the corresponding function in the user class. |
| 162 | + |
| 163 | +:::tip |
| 164 | + |
| 165 | +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: |
| 166 | +```4d |
| 167 | +var $options.onResponse:=Formula(myMethod) |
| 168 | +``` |
| 169 | +::: |
| 170 | + |
| 171 | + |
| 172 | + |
| 173 | + |
| 174 | +## Synchronous execution in asynchronous code |
| 175 | + |
| 176 | +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. |
| 177 | + |
| 178 | +The **`.wait()`** function pauses execution of the current process and puts 4D in [event listening](#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. |
| 179 | + |
| 180 | +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. |
| 181 | + |
| 182 | +Example: |
| 183 | + |
| 184 | +```4d |
| 185 | +var $options:=cs.Params.new() |
| 186 | +var $systemworker:=4D.SystemWorker.new("/bin/ls -l /Users ";$options) |
| 187 | +$systemworker.wait(0.5) // Waits for up to 0.5 seconds for get file info |
| 188 | +``` |
| 189 | + |
| 190 | +## See also |
| 191 | + |
| 192 | +[Blog post: Launch an external process asynchronously](https://blog.4d.com/launch-an-external-process-asynchronously/)<br/> |
| 193 | +[Asynchronous Call](../aikit/asynchronous-call.md) |
0 commit comments