Skill Tracer
Skills › Scientific Agent Skills › dask › futures.md

futures.md

554 lines · skills/dask/references/futures.md at 5f2bc03

Open on GitHub
1# Dask Futures
2
3Reviewed with Dask/distributed 2026.8.0. File paths, deployment settings, and undefined application functions are illustrative; executed local checks and current official sources are in [review.md](review.md).
4

Dask Futures

Reviewed with Dask/distributed 2026.8.0. File paths, deployment settings, and undefined application functions are illustrative; executed local checks and current official sources are in review.md.

Raw lines 1 to 4
5## Overview
6
7Dask futures extend Python's `concurrent.futures` interface, enabling immediate (non-lazy) task execution. Unlike delayed computations (used in DataFrames, Arrays, and Bags), futures provide more flexibility in situations where computations may evolve over time or require dynamic workflow construction.
8

Overview

Dask futures extend Python's concurrent.futures interface, enabling immediate (non-lazy) task execution. Unlike delayed computations (used in DataFrames, Arrays, and Bags), futures provide more flexibility in situations where computations may evolve over time or require dynamic workflow construction.

Raw lines 5 to 8
9## Core Concept
11Futures represent real-time task execution:
12- Tasks are scheduled immediately when submitted; execution waits for dependencies and resources
13- Each future represents a remote computation result
14- Automatic dependency tracking between futures
15- Enables dynamic, evolving workflows
16- Direct control over task scheduling and data placement

Core Concept

Futures represent real-time task execution:

  • Tasks are scheduled immediately when submitted; execution waits for dependencies and resources
  • Each future represents a remote computation result
  • Automatic dependency tracking between futures
  • Enables dynamic, evolving workflows
  • Direct control over task scheduling and data placement
Raw lines 9 to 17
18## Key Capabilities

Key Capabilities

Raw lines 18 to 19
20### Real-Time Execution
21- Submission schedules work without a separate compute call
22- No need for explicit `.compute()` call
23- Get results with `.result()` method

Real-Time Execution

  • Submission schedules work without a separate compute call
  • No need for explicit .compute() call
  • Get results with .result() method
Raw lines 20 to 24
25### Automatic Dependency Management
26When you submit tasks with future inputs, Dask automatically handles dependency tracking. Once all input futures have completed, they will be moved onto a single worker for efficient computation.

Automatic Dependency Management

When you submit tasks with future inputs, Dask automatically handles dependency tracking. Once all input futures have completed, they will be moved onto a single worker for efficient computation.

Raw lines 25 to 27
28### Dynamic Workflows
29Build computations that evolve based on intermediate results:
30- Submit new tasks based on previous results
31- Conditional execution paths
32- Iterative algorithms with varying structure

Dynamic Workflows

Build computations that evolve based on intermediate results:

  • Submit new tasks based on previous results
  • Conditional execution paths
  • Iterative algorithms with varying structure
Raw lines 28 to 33
34## When to Use Futures
36**Use Futures When**:
37- Building dynamic, evolving workflows
38- Need immediate task execution (not lazy)
39- Computations depend on runtime conditions
40- Require fine control over task placement
41- Implementing custom parallel algorithms
42- Need stateful computations (with actors)
44**Use Other Collections When**:
45- Static, predefined computation graphs (use delayed, DataFrames, Arrays)
46- Simple data parallelism on large collections (use Bags, DataFrames)
47- Standard array/dataframe operations suffice

When to Use Futures

Use Futures When:

  • Building dynamic, evolving workflows
  • Need immediate task execution (not lazy)
  • Computations depend on runtime conditions
  • Require fine control over task placement
  • Implementing custom parallel algorithms
  • Need stateful computations (with actors)

Use Other Collections When:

  • Static, predefined computation graphs (use delayed, DataFrames, Arrays)
  • Simple data parallelism on large collections (use Bags, DataFrames)
  • Standard array/dataframe operations suffice
Raw lines 34 to 48
49## Setting Up Client
51Futures require a distributed client:
53```python
54from dask.distributed import Client
56# Local cluster (on single machine)
57client = Client()
59# Or specify resources
60client = Client(n_workers=4, threads_per_worker=2)
62# Or connect to existing cluster
63client = Client('scheduler-address:8786')
64```

Setting Up Client

Futures require a distributed client:

from dask.distributed import Client

# Local cluster (on single machine)
client = Client()

# Or specify resources
client = Client(n_workers=4, threads_per_worker=2)

# Or connect to existing cluster
client = Client('scheduler-address:8786')
Raw lines 49 to 65
66## Submitting Tasks

Submitting Tasks

Raw lines 66 to 67
68### Basic Submit
69```python
70from dask.distributed import Client
72client = Client()
74# Submit single task
75def add(x, y):
76 return x + y
78future = client.submit(add, 1, 2)
80# Get result
81result = future.result() # Blocks until complete
82print(result) # 3
83```

Basic Submit

from dask.distributed import Client

client = Client()

# Submit single task
def add(x, y):
    return x + y

future = client.submit(add, 1, 2)

# Get result
result = future.result()  # Blocks until complete
print(result)  # 3
Raw lines 68 to 84
85### Multiple Tasks
86```python
87# Submit multiple independent tasks
88futures = []
89for i in range(10):
90 future = client.submit(add, i, i)
91 futures.append(future)
93# Gather results
94results = client.gather(futures) # Efficient parallel gathering
95```

Multiple Tasks

# Submit multiple independent tasks
futures = []
for i in range(10):
    future = client.submit(add, i, i)
    futures.append(future)

# Gather results
results = client.gather(futures)  # Efficient parallel gathering
Raw lines 85 to 96
97### Map Over Inputs
98```python
99# Apply function to multiple inputs
100def square(x):
101 return x ** 2
103# Submit batch of tasks
104futures = client.map(square, range(100))
106# Gather results
107results = client.gather(futures)
108```
110**Note**: Each task carries ~1ms overhead, making `map` less suitable for millions of tiny tasks. For massive datasets, use Bags or DataFrames instead.

Map Over Inputs

# Apply function to multiple inputs
def square(x):
    return x ** 2

# Submit batch of tasks
futures = client.map(square, range(100))

# Gather results
results = client.gather(futures)

Note: Each task carries ~1ms overhead, making map less suitable for millions of tiny tasks. For massive datasets, use Bags or DataFrames instead.

Raw lines 97 to 111
112## Working with Futures

Working with Futures

Raw lines 112 to 113
114### Check Status
115```python
116future = client.submit(expensive_function, arg)
118# Check if complete
119print(future.done()) # False or True
121# Check status
122print(future.status) # e.g. pending, finished, error, cancelled, or lost
123# Use client.processing() for tasks executing on workers.
124```

Check Status

future = client.submit(expensive_function, arg)

# Check if complete
print(future.done())  # False or True

# Check status
print(future.status)  # e.g. pending, finished, error, cancelled, or lost
# Use client.processing() for tasks executing on workers.
Raw lines 114 to 125
126### Non-Blocking Result Retrieval
127```python
128# Non-blocking check
129if future.done():
130 result = future.result()
131else:
132 print("Still computing...")
134# Or use callbacks
135def handle_result(future):
136 print(f"Result: {future.result()}")
138future.add_done_callback(handle_result)
139```

Non-Blocking Result Retrieval

# Non-blocking check
if future.done():
    result = future.result()
else:
    print("Still computing...")

# Or use callbacks
def handle_result(future):
    print(f"Result: {future.result()}")

future.add_done_callback(handle_result)
Raw lines 126 to 140
141### Error Handling
142```python
143def might_fail(x):
144 if x < 0:
145 raise ValueError("Negative value")
146 return x ** 2
148future = client.submit(might_fail, -5)
150try:
151 result = future.result()
152except ValueError as e:
153 print(f"Task failed: {e}")
154```

Error Handling

def might_fail(x):
    if x < 0:
        raise ValueError("Negative value")
    return x ** 2

future = client.submit(might_fail, -5)

try:
    result = future.result()
except ValueError as e:
    print(f"Task failed: {e}")
Raw lines 141 to 155
156## Task Dependencies

Task Dependencies

Raw lines 156 to 157
158### Automatic Dependency Tracking
159```python
160# Submit task
161future1 = client.submit(add, 1, 2)
163# Use future as input (creates dependency)
164future2 = client.submit(add, future1, 10) # Depends on future1
166# Chain dependencies
167future3 = client.submit(add, future2, 100) # Depends on future2
169# Get final result
170result = future3.result() # 113
171```

Automatic Dependency Tracking

# Submit task
future1 = client.submit(add, 1, 2)

# Use future as input (creates dependency)
future2 = client.submit(add, future1, 10)  # Depends on future1

# Chain dependencies
future3 = client.submit(add, future2, 100)  # Depends on future2

# Get final result
result = future3.result()  # 113
Raw lines 158 to 172
173### Complex Dependencies
174```python
175# Multiple dependencies
176a = client.submit(func1, x)
177b = client.submit(func2, y)
178c = client.submit(func3, a, b) # Depends on both a and b
180result = c.result()
181```

Complex Dependencies

# Multiple dependencies
a = client.submit(func1, x)
b = client.submit(func2, y)
c = client.submit(func3, a, b)  # Depends on both a and b

result = c.result()
Raw lines 173 to 182
183## Data Movement Optimization

Data Movement Optimization

Raw lines 183 to 184
185### Scatter Data
186Pre-scatter important data to avoid repeated transfers:
188```python
189# Upload data to cluster once
190[large_dataset] = client.scatter([big_data]) # One future, even for a list/dict
192# Use scattered data in multiple tasks
193futures = [client.submit(process, large_dataset, i) for i in range(100)]
195# Dask transfers dependencies to workers that need them; scatter is not zero-copy.
196results = client.gather(futures)
197```

Scatter Data

Pre-scatter important data to avoid repeated transfers:

# Upload data to cluster once
[large_dataset] = client.scatter([big_data])  # One future, even for a list/dict

# Use scattered data in multiple tasks
futures = [client.submit(process, large_dataset, i) for i in range(100)]

# Dask transfers dependencies to workers that need them; scatter is not zero-copy.
results = client.gather(futures)
Raw lines 185 to 198
199### Efficient Gathering
200Use `client.gather()` for concurrent result collection:
202```python
203# Better: Gather all at once (parallel)
204results = client.gather(futures)
206# Worse: Sequential result retrieval
207results = [f.result() for f in futures]
208```

Efficient Gathering

Use client.gather() for concurrent result collection:

# Better: Gather all at once (parallel)
results = client.gather(futures)

# Worse: Sequential result retrieval
results = [f.result() for f in futures]
Raw lines 199 to 209
210## Fire-and-Forget
212For side-effect tasks without needing the result:
214```python
215from dask.distributed import fire_and_forget
217def log_to_database(data):
218 # Write to database, no return value needed
219 database.write(data)
221# Submit without keeping reference
222future = client.submit(log_to_database, data, pure=False)
223fire_and_forget(future)
225# Retains interest after this Future is released; this is not exactly-once delivery.
226# External writes must be idempotent because tasks can be re-executed.
227```

Fire-and-Forget

For side-effect tasks without needing the result:

from dask.distributed import fire_and_forget

def log_to_database(data):
    # Write to database, no return value needed
    database.write(data)

# Submit without keeping reference
future = client.submit(log_to_database, data, pure=False)
fire_and_forget(future)

# Retains interest after this Future is released; this is not exactly-once delivery.
# External writes must be idempotent because tasks can be re-executed.
Raw lines 210 to 228
229## Performance Characteristics

Performance Characteristics

Raw lines 229 to 230
231### Task Overhead
232- ~1ms overhead per task
233- Good for: Thousands of tasks
234- Not suitable for: Millions of tiny tasks

Task Overhead

  • ~1ms overhead per task
  • Good for: Thousands of tasks
  • Not suitable for: Millions of tiny tasks
Raw lines 231 to 235
236### Worker-to-Worker Communication
237- Direct worker-to-worker data transfer
238- Roundtrip latency depends on hardware, serialization, and network
239- Efficient for task dependencies

Worker-to-Worker Communication

  • Direct worker-to-worker data transfer
  • Roundtrip latency depends on hardware, serialization, and network
  • Efficient for task dependencies
Raw lines 236 to 240
241### Memory Management
242Dask tracks active futures locally. When a future is garbage collected by your local Python session, Dask will feel free to delete that data.
244**Keep References**:
245```python
246# Keep reference to prevent deletion
247important_result = client.submit(expensive_calc, data)
249# Use result multiple times
250future1 = client.submit(process1, important_result)
251future2 = client.submit(process2, important_result)
252```

Memory Management

Dask tracks active futures locally. When a future is garbage collected by your local Python session, Dask will feel free to delete that data.

Keep References:

# Keep reference to prevent deletion
important_result = client.submit(expensive_calc, data)

# Use result multiple times
future1 = client.submit(process1, important_result)
future2 = client.submit(process2, important_result)
Raw lines 241 to 253
254## Advanced Coordination

Advanced Coordination

Raw lines 254 to 255
256### Distributed Primitives
258**Queues**:
259```python
260from dask.distributed import Queue
262queue = Queue()
264def producer():
265 for i in range(10):
266 queue.put(i)
268def consumer():
269 results = []
270 for _ in range(10):
271 results.append(queue.get())
272 return results
274# Submit tasks
275producer_future = client.submit(producer, pure=False)
276result_future = client.submit(consumer, pure=False)
277results = result_future.result()
278producer_future.result()
279queue.close()
280```
282Retain both futures and provide at least two runnable worker threads for blocking producer/consumer or event examples. Use finite timeouts in production; a blocked waiter can starve its setter. Queues send small messages or Futures through the scheduler.
284**Locks**:
285```python
286from dask.distributed import Lock
288lock = Lock()
290def critical_section():
291 with lock:
292 # Only one task executes this at a time
293 shared_resource.update()
294```
296Dask locks use leases: timeout/worker failures can permit overlapping holders; disabling lease expiry can deadlock. Use a transactional external service for durable write correctness.
298**Events**:
299```python
300from dask.distributed import Event
301import time
303event = Event()
305def waiter():
306 event.wait() # Blocks until event is set
307 return "Event occurred"
309def setter():
310 time.sleep(5)
311 event.set()
313# Start both tasks
314wait_future = client.submit(waiter, pure=False)
315set_future = client.submit(setter, pure=False)
317result = wait_future.result() # Waits for setter to complete
318```
320**Variables**:
321```python
322from dask.distributed import Variable
324var = Variable('my-var')
326# Set value
327var.set(42)
329# Get value from tasks
330def reader():
331 return var.get()
333future = client.submit(reader, pure=False)
334print(future.result()) # 42
335```

Distributed Primitives

Queues:

from dask.distributed import Queue

queue = Queue()

def producer():
    for i in range(10):
        queue.put(i)

def consumer():
    results = []
    for _ in range(10):
        results.append(queue.get())
    return results

# Submit tasks
producer_future = client.submit(producer, pure=False)
result_future = client.submit(consumer, pure=False)
results = result_future.result()
producer_future.result()
queue.close()

Retain both futures and provide at least two runnable worker threads for blocking producer/consumer or event examples. Use finite timeouts in production; a blocked waiter can starve its setter. Queues send small messages or Futures through the scheduler.

Locks:

from dask.distributed import Lock

lock = Lock()

def critical_section():
    with lock:
        # Only one task executes this at a time
        shared_resource.update()

Dask locks use leases: timeout/worker failures can permit overlapping holders; disabling lease expiry can deadlock. Use a transactional external service for durable write correctness.

Events:

from dask.distributed import Event
import time

event = Event()

def waiter():
    event.wait()  # Blocks until event is set
    return "Event occurred"

def setter():
    time.sleep(5)
    event.set()

# Start both tasks
wait_future = client.submit(waiter, pure=False)
set_future = client.submit(setter, pure=False)

result = wait_future.result()  # Waits for setter to complete

Variables:

from dask.distributed import Variable

var = Variable('my-var')

# Set value
var.set(42)

# Get value from tasks
def reader():
    return var.get()

future = client.submit(reader, pure=False)
print(future.result())  # 42
Raw lines 256 to 336
337## Actors
339For stateful, rapidly-changing workflows, actors bypass scheduler coordination for method calls; latency is deployment-dependent.

Actors

For stateful, rapidly-changing workflows, actors bypass scheduler coordination for method calls; latency is deployment-dependent.

Raw lines 337 to 340
341### Creating Actors
342```python
343from dask.distributed import Client
345client = Client()
347class Counter:
348 def __init__(self):
349 self.count = 0
351 def increment(self):
352 self.count += 1
353 return self.count
355 def get_count(self):
356 return self.count
358# Create actor on worker
359counter = client.submit(Counter, actor=True).result()
361# Call methods
362counter.increment().result()
363counter.increment().result()
364result = counter.get_count().result()
365print(result) # 2
366```
368Actor state has no automatic reconstruction after its worker is lost. Wait for each state mutation when later calls depend on its completion.

Creating Actors

from dask.distributed import Client

client = Client()

class Counter:
    def __init__(self):
        self.count = 0

    def increment(self):
        self.count += 1
        return self.count

    def get_count(self):
        return self.count

# Create actor on worker
counter = client.submit(Counter, actor=True).result()

# Call methods
counter.increment().result()
counter.increment().result()
result = counter.get_count().result()
print(result)  # 2

Actor state has no automatic reconstruction after its worker is lost. Wait for each state mutation when later calls depend on its completion.

Raw lines 341 to 369
370### Actor Use Cases
371- Stateful services (databases, caches)
372- Rapidly changing state
373- Complex coordination patterns
374- Real-time streaming applications

Actor Use Cases

  • Stateful services (databases, caches)
  • Rapidly changing state
  • Complex coordination patterns
  • Real-time streaming applications
Raw lines 370 to 375
376## Common Patterns

Common Patterns

Raw lines 376 to 377
378### Embarrassingly Parallel Tasks
379```python
380from dask.distributed import Client
382client = Client()
384def process_item(item):
385 # Independent computation
386 return expensive_computation(item)
388# Process many items in parallel
389items = range(1000)
390futures = client.map(process_item, items)
392# Gather all results
393results = client.gather(futures)
394```

Embarrassingly Parallel Tasks

from dask.distributed import Client

client = Client()

def process_item(item):
    # Independent computation
    return expensive_computation(item)

# Process many items in parallel
items = range(1000)
futures = client.map(process_item, items)

# Gather all results
results = client.gather(futures)
Raw lines 378 to 395
396### Dynamic Task Submission
397```python
398from dask.distributed import worker_client
400def recursive_sum(values):
401 if len(values) <= 2:
402 return sum(values)
403 midpoint = len(values) // 2
404 # worker_client releases this task's worker slot while awaiting children.
405 # Capturing a driver-side Client or simply blocking all workers can deadlock.
406 with worker_client() as nested:
407 left = nested.submit(recursive_sum, values[:midpoint])
408 right = nested.submit(recursive_sum, values[midpoint:])
409 return sum(nested.gather([left, right]))
411result = client.submit(recursive_sum, list(range(16))).result(timeout=30)
412assert result == 120
413```

Dynamic Task Submission

from dask.distributed import worker_client

def recursive_sum(values):
    if len(values) <= 2:
        return sum(values)
    midpoint = len(values) // 2
    # worker_client releases this task's worker slot while awaiting children.
    # Capturing a driver-side Client or simply blocking all workers can deadlock.
    with worker_client() as nested:
        left = nested.submit(recursive_sum, values[:midpoint])
        right = nested.submit(recursive_sum, values[midpoint:])
        return sum(nested.gather([left, right]))

result = client.submit(recursive_sum, list(range(16))).result(timeout=30)
assert result == 120
Raw lines 396 to 414
415### Parameter Sweep
416```python
417from itertools import product
419def run_simulation(param1, param2, param3):
420 # Run simulation with parameters
421 return simulate(param1, param2, param3)
423# Generate parameter combinations
424params = product(range(10), range(10), range(10))
426# Submit all combinations
427futures = [client.submit(run_simulation, p1, p2, p3) for p1, p2, p3 in params]
429# Gather results as they complete
430from dask.distributed import as_completed
432for future in as_completed(futures):
433 result = future.result()
434 process_result(result)
435```

Parameter Sweep

from itertools import product

def run_simulation(param1, param2, param3):
    # Run simulation with parameters
    return simulate(param1, param2, param3)

# Generate parameter combinations
params = product(range(10), range(10), range(10))

# Submit all combinations
futures = [client.submit(run_simulation, p1, p2, p3) for p1, p2, p3 in params]

# Gather results as they complete
from dask.distributed import as_completed

for future in as_completed(futures):
    result = future.result()
    process_result(result)
Raw lines 415 to 436
437### Pipeline with Dependencies
438```python
439# Stage 1: Load data
440load_futures = [client.submit(load_data, file) for file in files]
442# Stage 2: Process (depends on stage 1)
443process_futures = [client.submit(process, f) for f in load_futures]
445# Stage 3: Aggregate (depends on stage 2)
446agg_future = client.submit(aggregate, process_futures)
448# Get final result
449result = agg_future.result()
450```

Pipeline with Dependencies

# Stage 1: Load data
load_futures = [client.submit(load_data, file) for file in files]

# Stage 2: Process (depends on stage 1)
process_futures = [client.submit(process, f) for f in load_futures]

# Stage 3: Aggregate (depends on stage 2)
agg_future = client.submit(aggregate, process_futures)

# Get final result
result = agg_future.result()
Raw lines 437 to 451
452### Iterative Algorithm
453```python
454# Initialize
455[state] = client.scatter([initial_state])
457# Iterate
458for iteration in range(num_iterations):
459 # Compute update based on current state
460 state = client.submit(update_function, state)
462 # Check convergence
463 converged = client.submit(check_convergence, state)
464 if converged.result():
465 break
467# Get final state
468final_state = state.result()
469```

Iterative Algorithm

# Initialize
[state] = client.scatter([initial_state])

# Iterate
for iteration in range(num_iterations):
    # Compute update based on current state
    state = client.submit(update_function, state)

    # Check convergence
    converged = client.submit(check_convergence, state)
    if converged.result():
        break

# Get final state
final_state = state.result()
Raw lines 452 to 470
471## Best Practices

Best Practices

Raw lines 471 to 472
473### 1. Pre-scatter Large Data
474```python
475# Upload once, use many times
476[large_data] = client.scatter([big_dataset])
477futures = [client.submit(process, large_data, i) for i in range(100)]
478```

1. Pre-scatter Large Data

# Upload once, use many times
[large_data] = client.scatter([big_dataset])
futures = [client.submit(process, large_data, i) for i in range(100)]
Raw lines 473 to 479
480### 2. Use Gather for Bulk Retrieval
481```python
482# Efficient: Parallel gathering
483results = client.gather(futures)
485# Inefficient: Sequential
486results = [f.result() for f in futures]
487```

2. Use Gather for Bulk Retrieval

# Efficient: Parallel gathering
results = client.gather(futures)

# Inefficient: Sequential
results = [f.result() for f in futures]
Raw lines 480 to 488
489### 3. Manage Memory with References
490```python
491# Keep important futures
492important = client.submit(expensive_calc, data)
494# Use multiple times
495f1 = client.submit(use_result, important)
496f2 = client.submit(use_result, important)
498# Clean up when done
499del important
500```

3. Manage Memory with References

# Keep important futures
important = client.submit(expensive_calc, data)

# Use multiple times
f1 = client.submit(use_result, important)
f2 = client.submit(use_result, important)

# Clean up when done
del important
Raw lines 489 to 501
502### 4. Handle Errors Appropriately
503```python
504futures = client.map(might_fail, inputs)
506# Check for errors
507results = []
508errors = []
509for future in as_completed(futures):
510 try:
511 results.append(future.result())
512 except Exception as e:
513 errors.append(e)
514```

4. Handle Errors Appropriately

futures = client.map(might_fail, inputs)

# Check for errors
results = []
errors = []
for future in as_completed(futures):
    try:
        results.append(future.result())
    except Exception as e:
        errors.append(e)
Raw lines 502 to 515
516### 5. Use as_completed for Progressive Processing
517```python
518from dask.distributed import as_completed
520futures = client.map(process, items)
522# Process results as they arrive
523for future in as_completed(futures):
524 result = future.result()
525 handle_result(result)
526```

5. Use as_completed for Progressive Processing

from dask.distributed import as_completed

futures = client.map(process, items)

# Process results as they arrive
for future in as_completed(futures):
    result = future.result()
    handle_result(result)
Raw lines 516 to 527
528## Debugging Tips

Debugging Tips

Raw lines 528 to 529
530### Monitor Dashboard
531View the Dask dashboard to see:
532- Task progress
533- Worker utilization
534- Memory usage
535- Task dependencies

Monitor Dashboard

View the Dask dashboard to see:

  • Task progress
  • Worker utilization
  • Memory usage
  • Task dependencies
Raw lines 530 to 536
537### Check Task Status
538```python
539# Inspect future
540print(future.status)
541print(future.done())
543# Get traceback on error
544try:
545 future.result()
546except Exception:
547 print(future.traceback())
548```

Check Task Status

# Inspect future
print(future.status)
print(future.done())

# Get traceback on error
try:
    future.result()
except Exception:
    print(future.traceback())
Raw lines 537 to 549
550### Profile Tasks
551```python
552# Get performance data
553client.profile(filename='profile.html')
554```

Profile Tasks

# Get performance data
client.profile(filename='profile.html')
Raw lines 550 to 554