# JobScheduler

*Coordinate complex async work with declarative job graphs, dependency-aware scheduling, and built-in UI status reporting.*

The `JobScheduler` in `Ivy.Helpers` orchestrates asynchronous jobs, resolves dependencies, and exposes real-time state via reactive updates. Use [UseRef](../../../03_Hooks/02_Core/08_UseRef.md) to hold the scheduler instance, [UseRefreshToken](../../../03_Hooks/02_Core/16_UseRefreshToken.md) and [UseEffect](../../../03_Hooks/02_Core/04_UseEffect.md) to refresh the UI on job updates, and pair it with `JobSchedulerExtensions.ToView()` to render a hierarchical job monitor in your [layout](../../../01_Onboarding/02_Concepts/04_Layout.md).

## Basic Usage

Create a scheduler, define jobs, and use a button to trigger execution:

```csharp
public class JobsDashboard : ViewBase
{
    public override object? Build()
    {
        var scheduler = UseRef(BuildScheduler).Value;
        var refresh = UseRefreshToken();

        UseEffect(() => scheduler.Subscribe(_ => refresh.Refresh()));

        return Layout.Vertical()
            | new Button("Start Jobs", onClick: async _ => await scheduler.RunAsync())
            | scheduler.ToView();
    }

    private static JobScheduler BuildScheduler()
    {
        var scheduler = new JobScheduler(maxParallelJobs: 2);

        var initialize = scheduler.CreateJob("Initialize")
            .WithAction(async (_, _, progress, token) =>
            {
                await Task.Delay(300, token);
                progress.Report(1);
            })
            .Build();

        scheduler.CreateJob("Load Data")
            .DependsOn(initialize)
            .WithAction(async (_, _, progress, token) =>
            {
                await Task.Delay(500, token);
                progress.Report(1);
            })
            .Build();

        scheduler.CreateJob("Refresh Cache")
            .WithAction(async (_, _, progress, token) =>
            {
                for (var i = 0; i < 5; i++)
                {
                    await Task.Delay(100, token);
                    progress.Report((i + 1) / 5.0);
                }
            })
            .Build();

        return scheduler;
    }
}
```

> **tip:** Call `progress.Report(0..1)` inside the action to report progress. The scheduler clamps values and the built-in view renders a progress bar automatically.

## Job States

Jobs transition through these states automatically:

```mermaid
graph LR
    A[Waiting] --> B[Running]
    B --> C[Finished]
    B --> D[Failed]
    A --> E[Cancelled]
    B --> E
    
```

### Linear Dependencies

`DependsOn` enforces that a job waits for its prerequisites to finish before entering the queue.

```csharp
public class DependencyGraphDemo : ViewBase
{
    public override object? Build()
    {
        var scheduler = UseRef(BuildScheduler).Value;
        var refresh = UseRefreshToken();

        UseEffect(() => scheduler.Subscribe(_ => refresh.Refresh()));

        return Layout.Vertical()
            | new Button("Run Pipeline", onClick: async _ => await scheduler.RunAsync())
            | scheduler.ToView();
    }

    private static JobScheduler BuildScheduler()
    {
        var scheduler = new JobScheduler(maxParallelJobs: 1);

        var extract = scheduler.CreateJob("Extract Data")
            .WithAction(async (_, _, progress, token) =>
            {
                await Task.Delay(300, token);
                progress.Report(1);
            })
            .Build();

        var transform = scheduler.CreateJob("Transform Data")
            .DependsOn(extract)
            .WithAction(async (_, _, progress, token) =>
            {
                await Task.Delay(300, token);
                progress.Report(1);
            })
            .Build();

        scheduler.CreateJob("Load Data")
            .DependsOn(extract, transform)
            .WithAction(async (_, _, progress, token) =>
            {
                await Task.Delay(300, token);
                progress.Report(1);
            })
            .Build();

        return scheduler;
    }
}
```

`WithAction` overloads accept signatures ranging from `Func<Task>` to full access with `(Job, IJobScheduler, IProgress<double>, CancellationToken)`.

### Dynamically Adding Child Jobs

Children can be attached to a parent at build time or while the parent is running. When added during execution, the scheduler defers them until the parent finishes its action.

```csharp
public class DynamicChildrenDemo : ViewBase
{
    public override object? Build()
    {
        var scheduler = UseRef(BuildScheduler).Value;
        var refresh = UseRefreshToken();

        UseEffect(() => scheduler.Subscribe(_ => refresh.Refresh()));

        return Layout.Vertical()
            | new Button("Generate Reports", onClick: async _ => await scheduler.RunAsync())
            | scheduler.ToView();
    }

    private static JobScheduler BuildScheduler()
    {
        var scheduler = new JobScheduler(maxParallelJobs: 2);

        scheduler.CreateJob("Generate Reports")
            .WithAction(async (job, sched, _, token) =>
            {
                for (int i = 1; i <= 3; i++)
                {
                    var child = sched.CreateJob($"Report {i}")
                        .WithAction(async (_, _, progress, childToken) =>
                        {
                            await Task.Delay(200, childToken);
                            progress.Report(1);
                        })
                        .Build();

                    sched.AddChild(job, child);
                }

                await Task.Delay(600, token);
            })
            .Build();

        return scheduler;
    }
}
```

### Fluent Job Chaining

Use `Then()` to chain dependent jobs fluently:

```csharp
public class FluentChainingDemo : ViewBase
{
    public override object? Build()
    {
        var scheduler = UseRef(BuildScheduler).Value;
        var refresh = UseRefreshToken();

        UseEffect(() => scheduler.Subscribe(_ => refresh.Refresh()));

        return Layout.Vertical()
            | new Button("Run Pipeline", onClick: async _ => await scheduler.RunAsync())
            | scheduler.ToView();
    }

    private static JobScheduler BuildScheduler()
    {
        var scheduler = new JobScheduler(maxParallelJobs: 1);

        scheduler.CreateJob("Step 1: Extract")
            .WithAction(async (_, _, progress, token) =>
            {
                await Task.Delay(300, token);
                progress.Report(1);
            })
            .Then("Step 2: Transform", async (_, _, progress, token) =>
            {
                await Task.Delay(300, token);
                progress.Report(1);
            })
            .Then("Step 3: Load", async (_, _, progress, token) =>
            {
                await Task.Delay(300, token);
                progress.Report(1);
            })
            .Build();

        return scheduler;
    }
}
```

### Multiple Children with Progress

Create complex job hierarchies with multiple children that report progress:

```csharp
public class MultipleChildrenDemo : ViewBase
{
    public override object? Build()
    {
        var scheduler = UseRef(BuildScheduler).Value;
        var refresh = UseRefreshToken();

        UseEffect(() => scheduler.Subscribe(_ => refresh.Refresh()));

        return Layout.Vertical()
            | new Button("Start Processing", onClick: async _ => await scheduler.RunAsync())
            | scheduler.ToView();
    }

    private static JobScheduler BuildScheduler()
    {
        var scheduler = new JobScheduler(maxParallelJobs: 3);

        Job jobA = scheduler.CreateJob("Job A")
            .WithAction(async (job, sched, _, token) =>
            {
                // Create multiple children with progress reporting
                for (int i = 1; i <= 5; i++)
                {
                    var child = sched.CreateJob($"Child A-{i}")
                        .WithAction(async (_, _, progress, childToken) =>
                        {
                            for (int j = 0; j <= 100; j++)
                            {
                                await Task.Delay(30, childToken);
                                progress.Report(j / 100.0);
                            }
                        })
                        .Build();

                    sched.AddChild(job, child);
                }

                await Task.Delay(500, token);
            })
            .Build();

        // Job B depends on Job A
        scheduler.CreateJob("Job B")
            .DependsOn(jobA)
            .WithAction(async (_, _, progress, token) =>
            {
                await Task.Delay(400, token);
                progress.Report(1);
            })
            .Build();

        // Job C is independent
        scheduler.CreateJob("Job C")
            .WithAction(async (_, _, progress, token) =>
            {
                await Task.Delay(600, token);
                progress.Report(1);
            })
            .Build();

        return scheduler;
    }
}
```

## Reference

| Method | Description |
|--------|-------------|
| `JobScheduler(int maxParallelJobs)` | Constructor that controls concurrency and lifecycle. |
| `JobScheduler.CreateJob(string title)` | Creates a new job builder. Returns `JobBuilder`. |
| `JobBuilder.WithTitle(string title)` | Sets or updates the job title. |
| `JobBuilder.WithAction(...)` | Registers job logic. Supports multiple overloads. |
| `JobBuilder.DependsOn(params Job[] jobs)` | Sets job prerequisites. |
| `JobBuilder.WithContinueOnChildFailure(bool)` | Keeps parents alive when children fail. |
| `JobBuilder.Then(...)` | Chains dependent jobs fluently. |
| `JobBuilder.Build()` | Builds and registers the job with the scheduler. |
| `Job.SetDisplay(object? display)` | Attaches custom status UI to the job. |
| `JobScheduler.AddChild(Job parent, Job child)` | Links dynamic child work to a parent job. |
| `JobSchedulerExtensions.ToView()` | Renders the scheduler state using Ivy [widgets](../../../01_Onboarding/02_Concepts/03_Widgets.md). |
| `JobScheduler.RunAsync(CancellationToken?)` | Starts scheduling and waits until all jobs settle. |
| `JobScheduler.CancelAll()` | Requests cancellation on all running jobs. |
| `JobScheduler.AllCompleted()` | Returns `true` when every job is `Finished`. |
| `JobScheduler.Subscribe(IObserver<Job> observer)` | Yields each job update for state refresh or logging. |