Futures: submit, as_completed and errors

threading & multiprocessing · Practical · 11 min

Futures give fine-grained control: per-task results, timeouts, progress reporting and error handling.

executor.submit(fn, *args, **kwargs) schedules one call and returns a Future, a handle to a result that will exist later.

  • fut.result(timeout=None) waits and returns the value, or re-raises the exception from the call.
  • fut.exception() returns the exception instead of raising.
  • fut.done(), fut.running(), fut.cancel() (only succeeds if the task hasn't started), fut.add_done_callback(fn).

Process results as they complete, not in submission order, with as_completed:

futures = {pool.submit(fetch, u): u for u in urls}
for fut in as_completed(futures):
    url = futures[fut]
    try:
        data = fut.result()
    except Exception as e:
        log.warning("%s failed: %s", url, e)

The dict maps each future back to its input, a common idiom.

concurrent.futures.wait(fs, timeout, return_when=FIRST_COMPLETED | FIRST_EXCEPTION | ALL_COMPLETED) returns (done, not_done) sets.

executor.shutdown(wait=True, cancel_futures=False) is what the with block calls on exit. Pass cancel_futures=True (3.9+) to drop queued tasks, for example after a fatal error or Ctrl+C. Running tasks can't be interrupted; they must finish on their own.

Example

import time
from concurrent.futures import ThreadPoolExecutor, as_completed

def work(n):
    time.sleep(0.1 * (4 - n))          # later items finish first
    if n == 2:
        raise RuntimeError("task 2 broke")
    return n * 10

with ThreadPoolExecutor(max_workers=4) as pool:
    futures = {pool.submit(work, n): n for n in range(4)}
    for fut in as_completed(futures):
        n = futures[fut]
        try:
            print(f"task {n} -> {fut.result()}")
        except RuntimeError as e:
            print(f"task {n} failed: {e}")
# task 3 -> 30
# task 2 failed: task 2 broke
# task 1 -> 10
# task 0 -> 0

Remember

  • submit() returns a Future; result() returns or re-raises
  • as_completed yields futures in completion order
  • shutdown(cancel_futures=True) drops queued tasks

Watch out for

  • Never calling result() on submitted futures, so their exceptions are silently lost.

Check yourself

  1. A submitted function raised ValueError. What does fut.result() do?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/concurrent.futures.html#future-objects