async
Asynchronous workers and tasks
Import
_ <- fat.async
Types
The async
library introduces the Worker
type.
Worker
The Worker
is a simple wrapper around an asynchronous operation.
Constructor
Name | Signature | Brief |
---|---|---|
Worker | (task: Method, wait: Number) | Builds a Worker in standby mode |
The Worker
constructor takes two arguments:
- task: The method to be executed asynchronously (the method may not take arguments directly, but you may curry those in using two arrows on the definition
-> ->
). - wait (optional): A timeout in milliseconds. If the task does not finish within this time, it is cancelled.
Prototype members
Name | Signature | Brief |
---|---|---|
start | (): Worker | Begins the task |
cancel | (): Void | Cancels the task |
await | (): Worker | Waits for task completion |
isDone | (): Boolean | Checks if the task has completed |
hasStarted | Boolean | Set by start method |
hasAwaited | Boolean | Set by await method |
isCanceled | Boolean | Set by cancel method |
result | Any | Set by await method |
Standalone Methods
Name | Signature | Brief |
---|---|---|
atomic | (op: Method): Any | Executes the operation atomically |
selfCancel | (): Void | Terminates the execution of the thread |
processors | (): Number | Get the number of processors |
Usage Notes
Worker
instances are mapped to system threads on a one-to-one basis and get executed as per the system's scheduling. This implies that their execution may not always be immediate. To wait for the result of a Worker
, employ the await
method.
Unlike in other contexts, in asynchronous code, the task: Method
executes without access to the scope in which it is created. It can only access properties that have been 'curried' -> ->
into its execution scope or those that are directly accessible in the global scope.
The global memory limit is shared by all Workers
, but a completely new context, including a separate stack, is allocated for each one. However, in the event of an irrecoverable or fatal error, such as memory or stack exhaustion by one of the Workers
, the interpreter will be halted and all threads terminated.
to keep maximum performance, avoid using text interpolation within asynchronous tasks
Examples
async <- fat.async
math <- fat.math
time <- fat.time
# Define a slow task
slowTask = (seconds: Number): Text -> -> {
time.wait(seconds * 1000)
'done'
}
# Start the task as a Worker
worker = Worker(slowTask(5)).start
# Get the worker result
result1 = worker.await.result # blocks until task is done
# Start a task with timeout
task = Worker(slowTask(5), 3000).start # task should timed out
# Get the task result
result2 = task.await.result # blocks until task is done or timeout occurs
the
await
method will raiseAsyncError
if the task times out before completion
atomic
The atomic
wrapper is a critical tool for ensuring thread safety and data integrity in concurrent programming. When multiple workers or asynchronous tasks access and modify shared resources, race conditions can occur, leading to unpredictable and erroneous outcomes. The atomic
operation addresses this issue by guaranteeing that the method it wraps is executed atomically. This means the entire operation is completed as a single, indivisible unit, with no possibility of other threads intervening partway through for the same operation. This is particularly important for operations such as incrementing a counter, updating shared data structures or files, or performing any action where the order of execution matters:
async.atomic(-> file.append(logFile, line))
While atomic
operations are a powerful tool for ensuring consistency, it's important to be mindful of the potential for contention it introduces. Contention occurs when multiple threads or tasks attempt to execute an operation simultaneously, leading to potential performance bottlenecks as each thread waits its turn. Overuse or unnecessary use of atomic
operations can significantly degrade the performance of your application by reducing concurrency. Keep only the critical section of code that absolutely requires atomicity enclosed as an atomic
operation.
under the hood, atomic operations are fundamentally guarded by a single global
mutex
Async in Web Build
When using fry
built with Emscripten (for example, when using FatScript Playground), the platform's limited support for multi-threading affects the Worker
implementation. To maximize cross-platform code compatibility, Worker
tasks execute inline and block the main thread when the start
method is called. This approach compromises the advantages of asynchronous execution but allows a consistent implementation across platforms in many scenarios.