app.thread - Concurrency/Coroutine API

Background tasks and concurrency control.

Note: The thread API is now under the app.thread namespace.

Methods

app.thread.create(func)

Creates and starts a background coroutine.

Parameters:

  • func (function) - Function to execute in the background

Returns: number - Coroutine ID

local tid = app.thread.create(function()
    -- Time-consuming operations (e.g., shell commands, HTTP requests)
    local result = app.shell.execute("find ~/Downloads -name '*.zip'")
    return result
end)

app.thread.wait(tid, timeout?)

Waits for a coroutine to complete and gets its result.

Parameters:

  • tid (number) - Coroutine ID
  • timeout (number, optional) - Timeout in milliseconds

Returns: any - Coroutine return value

local tid = app.thread.create(function()
    app.thread.sleep(2000)  -- 2 seconds
    return "Done"
end)

local result = app.thread.wait(tid)  -- Blocking wait
app.log.info(result)  -- "Done"

-- With timeout
local result = app.thread.wait(tid, 5000)  -- Wait up to 5 seconds

app.thread.waitAll(tids, timeout?)

Waits for all coroutines to complete.

Parameters:

  • tids (table) - Array of coroutine IDs
  • timeout (number, optional) - Timeout in milliseconds

Returns: table - Array of results (corresponding to input order)

local tids = {}
for i, file in ipairs(files) do
    tids[i] = app.thread.create(function()
        return processFile(file)
    end)
end

local results = app.thread.waitAll(tids)

app.thread.waitAny(tids, timeout?)

Waits for any one coroutine to complete.

Parameters:

  • tids (table) - Array of coroutine IDs
  • timeout (number, optional) - Timeout in milliseconds

Returns: table - {tid = completed coroutine ID, result = return value}

local tid1 = app.thread.create(function() return fetchFromServer1() end)
local tid2 = app.thread.create(function() return fetchFromServer2() end)

-- Use the result from whichever returns first
local first = app.thread.waitAny({tid1, tid2})
app.log.info("Coroutine " .. first.tid .. " finished first")

app.thread.cancel(tid)

Cancels a coroutine.

Parameters:

  • tid (number) - Coroutine ID

Returns: boolean - Whether the cancellation was successful

local tid = app.thread.create(function()
    -- Long-running task
end)

-- Cancel after timeout
app.thread.sleep(5000)
if thread.status(tid) == "running" then
    app.thread.cancel(tid)
end

Note: Cancellation only takes effect when the coroutine is suspended. I/O operations in progress (e.g., shell commands) cannot be cancelled mid-execution.

app.thread.cancelAll()

Cancels all coroutines.

-- Stop all background tasks when user clicks cancel
app.thread.cancelAll()

thread.status(tid)

Gets the coroutine status.

Parameters:

  • tid (number) - Coroutine ID

Returns: string - Status

Status Description
pending Waiting to execute
running Executing
suspended Suspended (waiting for I/O)
completed Completed
failed Execution failed
cancelled Cancelled
local status = thread.status(tid)
if status == "completed" then
    local result = app.thread.wait(tid)
end

app.thread.sleep(ms)

Pauses the current coroutine execution.

Parameters:

  • ms (number) - Pause duration in milliseconds
app.thread.create(function()
    app.log.info("Start")
    app.thread.sleep(2000)  -- Pause for 2 seconds
    app.log.info("Continue")
end)

thread.activeCount()

Gets the number of currently active coroutines.

Returns: number

local count = thread.activeCount()
app.log.info("Active coroutines: " .. count)

Examples

Parallel File Processing

function MyPlugin:handleBatchProcess(context)
    local files = context.selectedFiles

    app.progress.show("Processing", {total = #files})

    -- Create all coroutines
    local tids = {}
    for i, file in ipairs(files) do
        tids[i] = app.thread.create(function()
            return self:processFile(file)
        end)
    end

    -- Wait for all to complete
    local results = app.thread.waitAll(tids)

    app.progress.hide()

    -- Tally results
    local success = 0
    for _, r in ipairs(results) do
        if r then success = success + 1 end
    end

    app.notification.show("Done", "Successfully processed " .. success .. "/" .. #files .. " files")
end

Network Request with Timeout

function MyPlugin:fetchWithTimeout(url, timeout)
    local tid = app.thread.create(function()
        return app.http.get(url)
    end)

    local result = app.thread.wait(tid, timeout or 30000)

    if thread.status(tid) ~= "completed" then
        app.thread.cancel(tid)
        return nil, "Request timed out"
    end

    return result
end

Race Mode (Use the Fastest Result)

function MyPlugin:fetchFromMirrors(mirrors)
    local tids = {}
    for i, url in ipairs(mirrors) do
        tids[i] = app.thread.create(function()
            return app.http.get(url)
        end)
    end

    -- Use the result from whichever returns first
    local first = app.thread.waitAny(tids)

    -- Cancel the other requests
    for _, tid in ipairs(tids) do
        if thread.status(tid) == "running" then
            app.thread.cancel(tid)
        end
    end

    return first.result
end

Notes

  1. Use cases: Coroutines are suited for I/O-intensive tasks (shell commands, HTTP requests, file operations), not for CPU-intensive computation
  2. Concurrency count: It is recommended to run no more than 8 coroutines simultaneously to avoid resource contention
  3. Cancellation limitations: I/O operations in progress cannot be cancelled immediately; the cancellation status can only be detected after the operation completes
  4. Error handling: Errors within a coroutine will set its status to failed; calling app.thread.wait() will return nil
Developer Documentation
User Guide
Getting Started Script Menus FAQ
Script Development
Development Guide
Plugin Development
Quick Start Development Guide Example Plugins
API Reference
Overview API Query Plugin Info Logging Finder Context Plugin Settings Internationalization
UI & Interaction
Dialog Progress Notification Chooser WebView Status Bar Dock
Files & Paths
File Operations Path Utilities Finder Actions Trash Extended Attributes Metadata File Watcher
Data Formats
JSON Plist CSV XML PDF Image
Text & Encoding
String Regex Date & Time Color Crypto
System
Shell Commands Process Application System Info AppleScript Shortcuts
System Info
Network Power/Battery Screen/Appearance Audio Bluetooth Location
Network
HTTP WebSocket URL
Input & Clipboard
Keyboard Mouse Hotkey Clipboard Window
Storage
SQLite Keychain UserDefaults
Media
OCR QR Code
Utilities
Archive UTI Share Timer Wake Lock Thread