Dex – 1: Threads for Blocking APIs
Description: Reference for Dex-1: Threads for Blocking APIs

#### Threads for Blocking APIs

There are often cases where you need to integrate with blocking APIs that do not fit well into the async or future-based models of the GNOME ecosystem. In those cases, you may want to use a dedicated thread for blocking calls so that you do not disrupt main loops, timeouts, or fiber scheduling.

# Creating a Dedicated Thread

Use the `dex_thread_spawn()` function to spawn a new thread. When the thread completes the resulting future will either resolve or reject.

    typedef DexFuture *(*DexThreadFunc) (gpointer user_data);

    DexFuture *future = dex_thread_spawn ("[my-thread]", thread_func, thread_data,
                                          (GDestroyNotify)thread_data_free);

# Waiting for Future Completion

Since dedicated threads do not have a `DexScheduler` on them and are not a fiber, you may not await futures. Awaiting would suspend a fiber stack but there is no such fiber to suspend.

To make integration easier, you may use `dex_thread_wait_for()` to wait for a future to complete. The mechanism used in this case is a mutex and condition variable which will be signaled when the dependent future completes.

# Managing a Fixed Thread Pool

Use `DexThreadPool` when you need bounded concurrency for blocking or foreign work. It is useful when you have a set of operations that can run in parallel, but you do not want to create an unbounded number of native threads with `dex_thread_spawn()`.

    DexThreadPool *pool = dex_thread_pool_new (4);

    DexFuture *future = dex_thread_pool_submit (pool,
                                                "[thumbnailer]",
                                                run_blocking_job,
                                                job_data,
                                                job_data_free);

The pool creates a fixed number of OS threads up front. Each submission is queued until a worker becomes available. The returned future resolves when the job completes, or rejects if the job returns an error future.

The optional `thread_name` passed to `dex_thread_pool_submit()` is applied to the returned future, not to the underlying OS thread. Use it to make the queued work visible in tracing and debugging output without paying the cost of renaming the worker itself.

If the pool is shared by multiple workloads and one workload needs a smaller concurrency budget, submit it through `dex_limiter_run_on_pool()`. That keeps the pool reusable while limiting how many jobs from that workload may run at once.

This is a good fit for:

- blocking filesystem or database work;
- wrapping legacy C libraries that do not integrate with GMainContext;
- limiting concurrency when the external dependency is expensive or not thread-safe at high fanout.

Important caveats:

- Pool workers do not have a `DexScheduler`, so you cannot `await()` other Dex futures from inside a worker function.
- The pool is for blocking work only. Do not use it for normal async work that already integrates with the main loop.
- `dex_thread_pool_close()` is asynchronous. Call it once, keep the returned future, and await that future to know shutdown is complete.
- Use `DEX_THREAD_POOL_SHUTDOWN_DRAIN` to let queued work finish, or `DEX_THREAD_POOL_SHUTDOWN_CANCEL_QUEUED` to reject anything still waiting in the queue.
- Call `close()` before dropping your last reference if there is any chance the pool is still active. That avoids finalization waiting for worker threads in an unexpected place.

In practice, the pattern is:

1. Create the pool with the number of concurrent workers you want.
2. Submit each blocking task with `dex_thread_pool_submit()`.
3. Hold onto the returned future if you need the result.
4. Close the pool with `dex_thread_pool_close()`.
5. Wait for the close future before letting the pool go out of scope.