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 (): * 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.

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 raise AsyncError 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.

See also

results matching ""

    No results matching ""