Shiny Jobs
Shiny.Jobs provides cross-platform background job scheduling and execution. On iOS and Android it uses the native OS schedulers (BGTaskScheduler / AndroidX WorkManager); on Windows it uses COM-activated background tasks. On plain .NET targets (Linux, macOS server, Blazor WASM, console, etc.) it runs an in-process managed JobManager driven by a recurring timer — jobs run only while the host process is alive; there is no OS-level scheduler on those targets. All platforms support the same constraints (network availability, charging, battery).
Blazor WASM caveat: background jobs only run while the tab is open and foregrounded. Service Worker / Periodic Background Sync cannot invoke C# because the SW has no access to the Blazor WASM runtime. For true background HTTP work on Blazor, use
Shiny.Net.Http.Blazor.
When to Use This Skill
- The user wants to schedule background work that runs periodically
- The user needs to run tasks when the app is not in the foreground
- The user asks about job constraints (network, charging, battery)
- The user wants to register or manage background jobs
- The user needs foreground job execution on a timer
- The user asks about
IJobManager,IJob,JobRegistration, or related types
Library Overview
| Item | Value |
|---|---|
| NuGet | Shiny.Jobs |
| Namespace | Shiny.Jobs (types); Shiny (registration extensions) |
| Platforms | iOS, tvOS, Android, Windows (native OS); Linux, macOS, Blazor WASM, .NET base (in-process) |
tvOS
tvOS carries the same BGTaskScheduler iOS does, so jobs schedule and run identically — same BGTaskSchedulerPermittedIdentifiers and processing background mode in Info.plist, same "the OS decides when" semantics. As on iOS, jobs do not run on the simulator. There is no MAUI on tvOS, so register through Shiny.Hosting.Native, not UseShiny().
Setup
Register jobs during service configuration. Each job is registered by its CLR type using AddJob<TJob> with optional fluent configuration. The job's Type is its unique identity — there is no separate string identifier.
using Shiny;
using Shiny.Jobs;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder.Services.AddJob<MySyncJob>(r => r
.WithForeground()
.WithInternet(InternetAccess.Any)
.WithBatteryNotLow()
);
return builder.Build();
}
}
AddJob<TJob> registers the infrastructure on first call (job manager + lifecycle task + battery/connectivity dependencies), then adds your job. Subsequent calls only add jobs.
iOS Setup
Add the following background task identifiers to your Info.plist under BGTaskSchedulerPermittedIdentifiers:
com.shiny.jobcom.shiny.jobpowercom.shiny.jobnetcom.shiny.jobpowernet
Also enable the processing background mode.
Android Setup
No additional manifest setup is required. Shiny.Jobs uses AndroidX WorkManager under the hood (minimum periodic interval: 15 minutes).
Windows Setup
Windows uses COM-activated in-process background tasks. Call ShinyJobsBackgroundTask.RegisterComServer() once in your App constructor before any trigger registration, and declare a matching windows.comServer extension in your appx manifest. Note: no background-mode triggering exists today on Windows — tasks fire while the process is running.
Plain .NET Setup (Linux, macOS, Blazor WASM, Console)
On the base .NET TFM there is no native OS scheduler — Shiny runs an in-process managed JobManager on a recurring timer (default 30s; configurable via the static JobManager.Interval property, minimum 15s, maximum 5 minutes). Jobs only execute while the host process is alive.
There is no separate Shiny.Jobs.Blazor package — reference Shiny.Jobs directly on all plain .NET targets (Blazor WASM included). You must register an IBattery and IConnectivity implementation; AddJob<TJob>(...) auto-registers them via AddConnectivity()/AddBattery() from the appropriate platform support package if present.
using Shiny;
using Shiny.Jobs;
// Linux / console — battery + connectivity come from Shiny.Core.Linux (via AddConnectivity/AddBattery)
// Blazor WASM — from Shiny.Support.DeviceMonitoring.Blazor
services.AddJob<MySyncJob>(r => r
.WithForeground()
.WithInternet(InternetAccess.Any)
);
Blazor WASM caveat: the in-process JobManager only runs while the tab is open. Background tabs are throttled (~1 min timer floor on Chromium), may be frozen after ~5 minutes, and iOS Safari kills background WASM aggressively. There is no way to run C# jobs via Service Worker Background Sync because the SW has no access to the WASM runtime. For background HTTP work specifically, use Shiny.Net.Http.Blazor.
Code Generation Instructions
When generating job-related code, follow these conventions:
- Implement
IJobdirectly for simple jobs, or inherit from theJobabstract base class for jobs that need a minimum interval between runs (MinimumTime). - The job's CLR type is its identity — do not invent a separate string identifier. Pass
typeof(TJob)toRunJob(...); iterateGetJobs()for the registered set. - Use constructor injection — job classes are resolved through DI. Inject dependencies through the constructor.
IJob.Runtakes onlyCancellationToken— there is noJobInfoargument. If a job needs configuration, inject it through the constructor or read from a settings store.- Honor the
CancellationToken— the platform may cancel a run on time-budget expiry; checkcancelToken.ThrowIfCancellationRequested()between work units. - Use
WithForeground()when the job should also execute periodically while the app is in the foreground (driven byJobLifecycleTask— platform builds only). - Use
WithInternet(InternetAccess.Any | Unmetered)to require connectivity before the job runs. - Use
WithCharging()/WithBatteryNotLow()for power-sensitive jobs — be aware these may delay a job indefinitely under hostile conditions. - Call
RequestAccess()(on the concreteJobManagerviaAbstractJobManager) before relying on background execution, so you can guide the user if a platform configuration is missing. - Register jobs at host build time via
services.AddJob<TJob>(...). There is no public runtime-register API onIJobManager.
Conventions
- Job class names should end with
Job(e.g.,DataSyncJob,CleanupJob). - Place job classes in a
Jobs/folder or namespace within the project. - Always handle exceptions inside
Rungracefully; unhandled exceptions are caught by the manager, logged, and surfaced via the returnedJobRunResult. - Job instances are resolved per run from the DI container — keep them scoped-safe (no long-lived per-job state outside DI).
Best Practices
- Check
RequestAccess()on startup and guide the user if background jobs are not available (AccessState.NotSetup,AccessState.Restricted,AccessState.Denied, etc.). - Keep jobs short-lived — background execution time is limited by the OS, especially on iOS where
BGProcessingTaskbudgets are short. - Use constraints wisely —
DeviceCharging = trueorBatteryNotLow = truemeans the job may not run for extended periods under hostile conditions. - Prefer
InternetAccess.AnyoverUnmeteredunless the job transfers large amounts of data. - Use the
Jobbase class withMinimumTimewhen the OS may invoke your job more often than necessary and you want a simple in-memory throttle. - Use
RunJob(typeof(TJob))for event-driven on-demand runs of a registered job. The job runs normally (inline); there is no extended-execution / background-task wrapping.
Reference Files
- API Reference