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

schedulers.md

528 lines · skills/dask/references/schedulers.md at 5f2bc03

Open on GitHub
1# Dask Schedulers
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 Schedulers

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 provides multiple task schedulers, each suited to different workloads. The scheduler determines how tasks are executed: sequentially, in parallel threads, in parallel processes, or distributed across a cluster.
8

Overview

Dask provides multiple task schedulers, each suited to different workloads. The scheduler determines how tasks are executed: sequentially, in parallel threads, in parallel processes, or distributed across a cluster.

Raw lines 5 to 8
9## Scheduler Types
11In scripts, guard process scheduler execution and local process-cluster creation with `if __name__ == "__main__":`. Array/DataFrame defaults below apply when no active distributed client or explicit configuration overrides them. Close both client and any separately created cluster when finished.

Scheduler Types

In scripts, guard process scheduler execution and local process-cluster creation with if __name__ == "__main__":. Array/DataFrame defaults below apply when no active distributed client or explicit configuration overrides them. Close both client and any separately created cluster when finished.

Raw lines 9 to 12
13### Single-Machine Schedulers
15#### 1. Local Threads (Default)
17**Description**: The threaded scheduler executes computations with a local `concurrent.futures.ThreadPoolExecutor`.
19**When to Use**:
20- Numeric computations in NumPy, Pandas, scikit-learn
21- Libraries that release the GIL (Global Interpreter Lock)
22- Operations benefit from shared memory access
23- Default for Dask Arrays and DataFrames
25**Characteristics**:
26- Low overhead
27- Shared memory between threads
28- Best for GIL-releasing operations
29- Poor for pure Python code (GIL contention)
31**Example**:
32```python
33import dask.array as da
35# Uses threads by default
36x = da.random.random((10000, 10000), chunks=(1000, 1000))
37result = x.mean().compute() # Computed with threads
38```
40**Explicit Configuration**:
41```python
42import dask
44# Set globally
45dask.config.set(scheduler='threads')
47# Or per-compute
48result = x.mean().compute(scheduler='threads')
49```
51#### 2. Local Processes
53**Description**: Multiprocessing scheduler that uses `concurrent.futures.ProcessPoolExecutor`.
55**When to Use**:
56- Pure Python code with GIL contention
57- Text processing and Python collections
58- Operations that benefit from process isolation
59- CPU-bound Python code
61**Characteristics**:
62- Bypasses GIL limitations
63- Incurs data transfer costs between processes
64- Higher overhead than threads
65- Ideal for linear workflows with small inputs/outputs
67**Example**:
68```python
69import dask.bag as db
71# Good for Python object processing
72bag = db.read_text('data/*.txt')
73result = bag.map(complex_python_function).compute(scheduler='processes')
74```
76**Explicit Configuration**:
77```python
78import dask
80# Set globally
81dask.config.set(scheduler='processes')
83# Or per-compute
84result = computation.compute(scheduler='processes')
85```
87**Limitations**:
88- Data must be serializable (pickle)
89- Overhead from process creation
90- Memory overhead from data copying
92#### 3. Single Thread (Synchronous)
94**Description**: The single-threaded synchronous scheduler executes all computations in the local thread with no parallelism at all.
96**When to Use**:
97- Debugging with pdb
98- Profiling with standard Python tools
99- Understanding errors in detail
100- Development and testing
102**Characteristics**:
103- No parallelism
104- Easy debugging
105- Low scheduler overhead
106- Serial execution; random functions and side effects still need reproducibility controls
108**Example**:
109```python
110import dask
112# Enable for debugging
113dask.config.set(scheduler='synchronous')
115# Now can use pdb
116result = computation.compute() # Runs in single thread
117```
119**Debugging with IPython**:
120```python
121# In IPython/Jupyter
122%pdb on
124dask.config.set(scheduler='synchronous')
125result = problematic_computation.compute() # Drops into debugger on error
126```

Single-Machine Schedulers

1. Local Threads (Default)

Description: The threaded scheduler executes computations with a local concurrent.futures.ThreadPoolExecutor.

When to Use:

  • Numeric computations in NumPy, Pandas, scikit-learn
  • Libraries that release the GIL (Global Interpreter Lock)
  • Operations benefit from shared memory access
  • Default for Dask Arrays and DataFrames

Characteristics:

  • Low overhead
  • Shared memory between threads
  • Best for GIL-releasing operations
  • Poor for pure Python code (GIL contention)

Example:

import dask.array as da

# Uses threads by default
x = da.random.random((10000, 10000), chunks=(1000, 1000))
result = x.mean().compute()  # Computed with threads

Explicit Configuration:

import dask

# Set globally
dask.config.set(scheduler='threads')

# Or per-compute
result = x.mean().compute(scheduler='threads')

2. Local Processes

Description: Multiprocessing scheduler that uses concurrent.futures.ProcessPoolExecutor.

When to Use:

  • Pure Python code with GIL contention
  • Text processing and Python collections
  • Operations that benefit from process isolation
  • CPU-bound Python code

Characteristics:

  • Bypasses GIL limitations
  • Incurs data transfer costs between processes
  • Higher overhead than threads
  • Ideal for linear workflows with small inputs/outputs

Example:

import dask.bag as db

# Good for Python object processing
bag = db.read_text('data/*.txt')
result = bag.map(complex_python_function).compute(scheduler='processes')

Explicit Configuration:

import dask

# Set globally
dask.config.set(scheduler='processes')

# Or per-compute
result = computation.compute(scheduler='processes')

Limitations:

  • Data must be serializable (pickle)
  • Overhead from process creation
  • Memory overhead from data copying

3. Single Thread (Synchronous)

Description: The single-threaded synchronous scheduler executes all computations in the local thread with no parallelism at all.

When to Use:

  • Debugging with pdb
  • Profiling with standard Python tools
  • Understanding errors in detail
  • Development and testing

Characteristics:

  • No parallelism
  • Easy debugging
  • Low scheduler overhead
  • Serial execution; random functions and side effects still need reproducibility controls

Example:

import dask

# Enable for debugging
dask.config.set(scheduler='synchronous')

# Now can use pdb
result = computation.compute()  # Runs in single thread

Debugging with IPython:

# In IPython/Jupyter
%pdb on

dask.config.set(scheduler='synchronous')
result = problematic_computation.compute()  # Drops into debugger on error
Raw lines 13 to 127
128### Distributed Schedulers
130#### 4. Local Distributed
132**Description**: Despite its name, this scheduler runs effectively on personal machines using the distributed scheduler infrastructure.
134**When to Use**:
135- Need diagnostic dashboard
136- Asynchronous APIs
137- Better data locality handling than multiprocessing
138- Development before scaling to cluster
139- Want distributed features on single machine
141**Characteristics**:
142- Provides dashboard for monitoring
143- Better memory management
144- More overhead than threads/processes
145- Can scale to cluster later
147**Example**:
148```python
149from dask.distributed import Client
150import dask.dataframe as dd
152# Create local cluster
153client = Client(n_workers=2, threads_per_worker=2) # Explicit resource budget
155# Use distributed scheduler
156ddf = dd.read_csv('data.csv')
157result = ddf.groupby('category')['value'].mean().compute()
159# View dashboard
160print(client.dashboard_link)
162# Clean up
163client.close()
164```
166**Configuration Options**:
167```python
168# Control resources
169client = Client(
170 n_workers=4,
171 threads_per_worker=2,
172 memory_limit='4GB'
174```
176#### 5. Cluster Distributed
178**Description**: For scaling across multiple machines using the distributed scheduler.
180**When to Use**:
181- Data exceeds single machine capacity
182- Need computational power beyond one machine
183- Production deployments
184- Cluster computing environments (HPC, cloud)
186**Characteristics**:
187- Scales to hundreds of machines
188- Requires cluster setup
189- Network communication overhead
190- Advanced features (adaptive scaling, task prioritization)
192**Example with Dask-Jobqueue (HPC)**:
193```python
194from dask_jobqueue import SLURMCluster
195from dask.distributed import Client
197# Create cluster on HPC with SLURM
198cluster = SLURMCluster(
199 cores=24,
200 memory='100GB',
201 walltime='02:00:00',
202 queue='regular'
205# Scale to 10 jobs
206cluster.scale(jobs=10)
208# Connect client
209client = Client(cluster)
211# Run computation
212result = computation.compute()
214client.close()
215cluster.close() # Release the jobs owned by this cluster
216```
218**Example with Dask on Kubernetes**:
219```python
220from dask_kubernetes.operator import KubeCluster
221from dask.distributed import Client
223# Illustrative: requires installed Dask Operator/CRDs and authorized kubeconfig.
224# Use an image with matching dask/distributed and workload dependencies.
225cluster = KubeCluster(name='research-dask', image=validated_worker_image)
226cluster.scale(20) # 20 workers
228client = Client(cluster)
229result = computation.compute()
231client.close()
232cluster.close()
233```
235Both deployment recipes require site-specific configuration and were not deployed in this review.

Distributed Schedulers

4. Local Distributed

Description: Despite its name, this scheduler runs effectively on personal machines using the distributed scheduler infrastructure.

When to Use:

  • Need diagnostic dashboard
  • Asynchronous APIs
  • Better data locality handling than multiprocessing
  • Development before scaling to cluster
  • Want distributed features on single machine

Characteristics:

  • Provides dashboard for monitoring
  • Better memory management
  • More overhead than threads/processes
  • Can scale to cluster later

Example:

from dask.distributed import Client
import dask.dataframe as dd

# Create local cluster
client = Client(n_workers=2, threads_per_worker=2)  # Explicit resource budget

# Use distributed scheduler
ddf = dd.read_csv('data.csv')
result = ddf.groupby('category')['value'].mean().compute()

# View dashboard
print(client.dashboard_link)

# Clean up
client.close()

Configuration Options:

# Control resources
client = Client(
    n_workers=4,
    threads_per_worker=2,
    memory_limit='4GB'
)

5. Cluster Distributed

Description: For scaling across multiple machines using the distributed scheduler.

When to Use:

  • Data exceeds single machine capacity
  • Need computational power beyond one machine
  • Production deployments
  • Cluster computing environments (HPC, cloud)

Characteristics:

  • Scales to hundreds of machines
  • Requires cluster setup
  • Network communication overhead
  • Advanced features (adaptive scaling, task prioritization)

Example with Dask-Jobqueue (HPC):

from dask_jobqueue import SLURMCluster
from dask.distributed import Client

# Create cluster on HPC with SLURM
cluster = SLURMCluster(
    cores=24,
    memory='100GB',
    walltime='02:00:00',
    queue='regular'
)

# Scale to 10 jobs
cluster.scale(jobs=10)

# Connect client
client = Client(cluster)

# Run computation
result = computation.compute()

client.close()
cluster.close()  # Release the jobs owned by this cluster

Example with Dask on Kubernetes:

from dask_kubernetes.operator import KubeCluster
from dask.distributed import Client

# Illustrative: requires installed Dask Operator/CRDs and authorized kubeconfig.
# Use an image with matching dask/distributed and workload dependencies.
cluster = KubeCluster(name='research-dask', image=validated_worker_image)
cluster.scale(20)  # 20 workers

client = Client(cluster)
result = computation.compute()

client.close()
cluster.close()

Both deployment recipes require site-specific configuration and were not deployed in this review.

Raw lines 128 to 236
237## Scheduler Configuration

Scheduler Configuration

Raw lines 237 to 238
239### Global Configuration
241```python
242import dask
244# Set scheduler globally for session
245dask.config.set(scheduler='threads')
246dask.config.set(scheduler='processes')
247dask.config.set(scheduler='synchronous')
248```

Global Configuration

import dask

# Set scheduler globally for session
dask.config.set(scheduler='threads')
dask.config.set(scheduler='processes')
dask.config.set(scheduler='synchronous')
Raw lines 239 to 249
250### Context Manager
252```python
253import dask
255# Temporarily use different scheduler
256with dask.config.set(scheduler='processes'):
257 result = computation.compute()
259# Back to default scheduler
260result2 = computation2.compute()
261```

Context Manager

import dask

# Temporarily use different scheduler
with dask.config.set(scheduler='processes'):
    result = computation.compute()

# Back to default scheduler
result2 = computation2.compute()
Raw lines 250 to 262
263### Per-Compute
265```python
266# Specify scheduler per compute call
267result = computation.compute(scheduler='threads')
268result = computation.compute(scheduler='processes')
269result = computation.compute(scheduler='synchronous')
270```

Per-Compute

# Specify scheduler per compute call
result = computation.compute(scheduler='threads')
result = computation.compute(scheduler='processes')
result = computation.compute(scheduler='synchronous')
Raw lines 263 to 271
272### Distributed Client
274```python
275from dask.distributed import Client
277# Using client automatically sets distributed scheduler
278client = Client()
280# All computations use distributed scheduler
281result = computation.compute()
283client.close()
284```

Distributed Client

from dask.distributed import Client

# Using client automatically sets distributed scheduler
client = Client()

# All computations use distributed scheduler
result = computation.compute()

client.close()
Raw lines 272 to 285
286## Choosing the Right Scheduler

Choosing the Right Scheduler

Raw lines 286 to 287
288### Decision Matrix
290| Workload Type | Recommended Scheduler | Rationale |
291|--------------|----------------------|-----------|
292| NumPy/Pandas operations | Threads (default) | GIL-releasing, shared memory |
293| Pure Python objects | Processes | Avoids GIL contention |
294| Text/log processing | Processes | Python-heavy operations |
295| Debugging | Synchronous | Easy debugging, deterministic |
296| Need dashboard | Local Distributed | Monitoring and diagnostics |
297| Multi-machine | Cluster Distributed | Exceeds single machine capacity |
298| Small data, quick tasks | Threads | Lowest overhead |
299| Large data, single machine | Local Distributed | Better memory management |

Decision Matrix

Workload TypeRecommended SchedulerRationale
NumPy/Pandas operationsThreads (default)GIL-releasing, shared memory
Pure Python objectsProcessesAvoids GIL contention
Text/log processingProcessesPython-heavy operations
DebuggingSynchronousEasy debugging, deterministic
Need dashboardLocal DistributedMonitoring and diagnostics
Multi-machineCluster DistributedExceeds single machine capacity
Small data, quick tasksThreadsLowest overhead
Large data, single machineLocal DistributedBetter memory management
Raw lines 288 to 300
301### Performance Considerations
303**Threads**:
304- Low overhead; measure with representative tasks
305- Best for: Numeric operations
306- Memory: Shared
307- GIL: Affected by GIL
309**Processes**:
310- Includes process startup and serialization costs
311- Best for: Python operations
312- Memory: Copied between processes
313- GIL: Not affected
315**Synchronous**:
316- Low overhead; no concurrent execution
317- Best for: Debugging
318- Memory: No parallelism
319- GIL: Not relevant
321**Distributed**:
322- Overhead: ~1 ms per task
323- Best for: Complex workflows, monitoring
324- Memory: Managed by scheduler
325- GIL: Workers can use threads or processes

Performance Considerations

Threads:

  • Low overhead; measure with representative tasks
  • Best for: Numeric operations
  • Memory: Shared
  • GIL: Affected by GIL

Processes:

  • Includes process startup and serialization costs
  • Best for: Python operations
  • Memory: Copied between processes
  • GIL: Not affected

Synchronous:

  • Low overhead; no concurrent execution
  • Best for: Debugging
  • Memory: No parallelism
  • GIL: Not relevant

Distributed:

  • Overhead: ~1 ms per task
  • Best for: Complex workflows, monitoring
  • Memory: Managed by scheduler
  • GIL: Workers can use threads or processes
Raw lines 301 to 326
327## Thread Configuration for Distributed Scheduler

Thread Configuration for Distributed Scheduler

Raw lines 327 to 328
329### Setting Thread Count
331```python
332from dask.distributed import Client
334# Control thread/worker configuration
335client = Client(
336 n_workers=4, # Number of worker processes
337 threads_per_worker=2 # Threads per worker process
339```

Setting Thread Count

from dask.distributed import Client

# Control thread/worker configuration
client = Client(
    n_workers=4,           # Number of worker processes
    threads_per_worker=2   # Threads per worker process
)
Raw lines 329 to 340
341### Recommended Configuration
343**For Numeric Workloads**:
344- Aim for roughly 4 threads per process
345- Balance between parallelism and overhead
346- Example: 8 cores → 2 workers with 4 threads each
348**For Python Workloads**:
349- Use more workers with fewer threads
350- Example: 8 cores → 8 workers with 1 thread each

Recommended Configuration

For Numeric Workloads:

  • Aim for roughly 4 threads per process
  • Balance between parallelism and overhead
  • Example: 8 cores → 2 workers with 4 threads each

For Python Workloads:

  • Use more workers with fewer threads
  • Example: 8 cores → 8 workers with 1 thread each
Raw lines 341 to 351
352### Configuration via dask.config
354Prefer `dask.config` over shell environment variables so worker settings stay explicit in code:
356```python
357import dask
359dask.config.set({
360 "distributed.worker.memory.target": 0.60,
361 "distributed.worker.memory.spill": 0.70,
362})
363```
365When creating a `Client`, pass resource limits directly:
367```python
368from dask.distributed import Client
370client = Client(n_workers=4, threads_per_worker=2, memory_limit='4GB')
371```
373Upstream also supports `DASK_*` environment variables (for example `DASK_SCHEDULER_ADDRESS` when connecting to an existing cluster). Set only the specific variables documented for your deployment — do not copy entire `.env` files into the environment.

Configuration via dask.config

Prefer dask.config over shell environment variables so worker settings stay explicit in code:

import dask

dask.config.set({
    "distributed.worker.memory.target": 0.60,
    "distributed.worker.memory.spill": 0.70,
})

When creating a Client, pass resource limits directly:

from dask.distributed import Client

client = Client(n_workers=4, threads_per_worker=2, memory_limit='4GB')

Upstream also supports DASK_* environment variables (for example DASK_SCHEDULER_ADDRESS when connecting to an existing cluster). Set only the specific variables documented for your deployment — do not copy entire .env files into the environment.

Raw lines 352 to 374
375## Common Patterns

Common Patterns

Raw lines 375 to 376
377### Development to Production
379```python
380# Development: Use local distributed for testing
381from dask.distributed import Client
382client = Client(processes=False) # In-process for debugging
384# Production: Scale to cluster
385from dask.distributed import Client
386client = Client('scheduler-address:8786')
387```

Development to Production

# Development: Use local distributed for testing
from dask.distributed import Client
client = Client(processes=False)  # In-process for debugging

# Production: Scale to cluster
from dask.distributed import Client
client = Client('scheduler-address:8786')
Raw lines 377 to 388
389### Mixed Workloads
391```python
392import dask
393import dask.dataframe as dd
395# Use threads for DataFrame operations
396ddf = dd.read_parquet('data.parquet')
397result1 = ddf['value'].mean().compute(scheduler='threads')
399# Use processes for Python code
400import dask.bag as db
401bag = db.read_text('logs/*.txt')
402result2 = bag.map(parse_log).compute(scheduler='processes')
403```

Mixed Workloads

import dask
import dask.dataframe as dd

# Use threads for DataFrame operations
ddf = dd.read_parquet('data.parquet')
result1 = ddf['value'].mean().compute(scheduler='threads')

# Use processes for Python code
import dask.bag as db
bag = db.read_text('logs/*.txt')
result2 = bag.map(parse_log).compute(scheduler='processes')
Raw lines 389 to 404
405### Debugging Workflow
407```python
408import dask
410# Step 1: Debug with synchronous scheduler
411dask.config.set(scheduler='synchronous')
412result = problematic_computation.compute()
414# Step 2: Test with threads
415dask.config.set(scheduler='threads')
416result = computation.compute()
418# Step 3: Scale with distributed
419from dask.distributed import Client
420client = Client()
421result = computation.compute()
422```

Debugging Workflow

import dask

# Step 1: Debug with synchronous scheduler
dask.config.set(scheduler='synchronous')
result = problematic_computation.compute()

# Step 2: Test with threads
dask.config.set(scheduler='threads')
result = computation.compute()

# Step 3: Scale with distributed
from dask.distributed import Client
client = Client()
result = computation.compute()
Raw lines 405 to 423
424## Monitoring and Diagnostics

Monitoring and Diagnostics

Raw lines 424 to 425
426### Dashboard Access (Distributed Only)
428```python
429from dask.distributed import Client
431client = Client()
433# Get dashboard URL
434print(client.dashboard_link)
435# Opens dashboard in browser showing:
436# - Task progress
437# - Worker status
438# - Memory usage
439# - Task stream
440# - Resource utilization
441```

Dashboard Access (Distributed Only)

from dask.distributed import Client

client = Client()

# Get dashboard URL
print(client.dashboard_link)
# Opens dashboard in browser showing:
# - Task progress
# - Worker status
# - Memory usage
# - Task stream
# - Resource utilization
Raw lines 426 to 442
443### Performance Profiling
445```python
446# Full report captures only the work inside the context. Requires Bokeh.
447from dask.distributed import performance_report
449with performance_report(filename='performance.html'):
450 result = computation.compute()
451# client.profile(filename='profile.html') gives statistical stack profiling only.
452```

Performance Profiling

# Full report captures only the work inside the context. Requires Bokeh.
from dask.distributed import performance_report

with performance_report(filename='performance.html'):
    result = computation.compute()
# client.profile(filename='profile.html') gives statistical stack profiling only.
Raw lines 443 to 453
454### Resource Monitoring
456```python
457# Check worker info
458client.scheduler_info()
460# Get current tasks
461client.who_has()
463# Host RAM percentage (not each worker's managed-memory usage)
464def host_memory_percent():
465 import psutil
466 return psutil.virtual_memory().percent
467client.run(host_memory_percent)
468```

Resource Monitoring

# Check worker info
client.scheduler_info()

# Get current tasks
client.who_has()

# Host RAM percentage (not each worker's managed-memory usage)
def host_memory_percent():
    import psutil
    return psutil.virtual_memory().percent
client.run(host_memory_percent)
Raw lines 454 to 469
470## Advanced Configuration

Advanced Configuration

Raw lines 470 to 471
472### Custom Executors
474```python
475from concurrent.futures import ThreadPoolExecutor
476import dask
478# Use custom thread pool
479with ThreadPoolExecutor(max_workers=4) as executor:
480 with dask.config.set(pool=executor):
481 result = computation.compute(scheduler='threads')
482```

Custom Executors

from concurrent.futures import ThreadPoolExecutor
import dask

# Use custom thread pool
with ThreadPoolExecutor(max_workers=4) as executor:
    with dask.config.set(pool=executor):
        result = computation.compute(scheduler='threads')
Raw lines 472 to 483
484### Adaptive Scaling (Distributed)
486```python
487from dask.distributed import Client
489client = Client()
491# Enable adaptive scaling
492client.cluster.adapt(minimum=2, maximum=10)
494# Cluster scales based on workload
495result = computation.compute()
496```

Adaptive Scaling (Distributed)

from dask.distributed import Client

client = Client()

# Enable adaptive scaling
client.cluster.adapt(minimum=2, maximum=10)

# Cluster scales based on workload
result = computation.compute()
Raw lines 484 to 497
498### Worker Plugins
500```python
501from dask.distributed import Client, WorkerPlugin
503class CustomPlugin(WorkerPlugin):
504 def setup(self, worker):
505 # Initialize worker-specific resources
506 worker.custom_resource = initialize_resource()
508client = Client()
509client.register_plugin(CustomPlugin(), name='custom-resource')
510```

Worker Plugins

from dask.distributed import Client, WorkerPlugin

class CustomPlugin(WorkerPlugin):
    def setup(self, worker):
        # Initialize worker-specific resources
        worker.custom_resource = initialize_resource()

client = Client()
client.register_plugin(CustomPlugin(), name='custom-resource')
Raw lines 498 to 511
512## Troubleshooting

Troubleshooting

Raw lines 512 to 513
514### Slow Performance with Threads
515**Problem**: Pure Python code slow with threaded scheduler
516**Solution**: Switch to processes or distributed scheduler

Slow Performance with Threads

Problem: Pure Python code slow with threaded scheduler Solution: Switch to processes or distributed scheduler

Raw lines 514 to 517
518### Memory Errors with Processes
519**Problem**: Data too large to pickle/copy between processes
520**Solution**: Use threaded or distributed scheduler

Memory Errors with Processes

Problem: Data too large to pickle/copy between processes Solution: Use threaded or distributed scheduler

Raw lines 518 to 521
522### Debugging Difficult
523**Problem**: Can't use pdb with parallel schedulers
524**Solution**: Use synchronous scheduler for debugging

Debugging Difficult

Problem: Can't use pdb with parallel schedulers Solution: Use synchronous scheduler for debugging

Raw lines 522 to 525
526### Task Overhead High
527**Problem**: Many tiny tasks causing overhead
528**Solution**: Use threaded scheduler (lowest overhead) or increase chunk sizes

Task Overhead High

Problem: Many tiny tasks causing overhead Solution: Use threaded scheduler (lowest overhead) or increase chunk sizes

Raw lines 526 to 528