Scheduler Net: Effortless Team Scheduling Made Simple

Written by

in

Here is a comprehensive guide to building a custom .NET scheduler application. How to Build a Custom Scheduler .NET Application

Building a custom scheduler in .NET allows you to automate background tasks like sending emails, processing data, or clearing logs. While third-party libraries exist, building your own gives you full control over execution logic and dependencies. This guide covers the architectural options, core implementation using BackgroundService, and production best practices. 1. Choose Your Architecture

Before writing code, choose the hosting pattern that fits your infrastructure needs.

Monolith Integration: Embedded directly inside an ASP.NET Core web API. It shares memory and database connections with your main application.

Isolated Worker Service: A standalone .NET Console application running as a Windows Service or Linux Daemon. It keeps background processing overhead away from your web servers.

Serverless/Cloud: Azure Functions or AWS Lambda triggered by cron expressions. Best for event-driven, intermittent tasks. 2. Core Implementation Using BackgroundService

The modern, native way to build a scheduler in .NET is by extending the BackgroundService class, which implements the IHostedService interface. Step 1: Create the Worker Project Create a new .NET Worker Service project via your terminal: dotnet new worker -n CustomSchedulerApp Use code with caution. Step 2: Implement the Scheduled Worker

Create a class that manages the timing loop. This example uses PeriodicTimer, introduced in modern .NET, which fixes the drift issues common with older timer variants.

using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; public class CustomScheduler : BackgroundService { private readonly ILogger _logger; private readonly TimeSpan _period = TimeSpan.FromMinutes(15); // Run every 15 minutes public CustomScheduler(ILogger logger) { _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation(“Custom Scheduler Service started.”); using PeriodicTimer timer = new(_period); while (!stoppingToken.IsCancellationRequested && await timer.WaitForNextTickAsync(stoppingToken)) { try { _logger.LogInformation(“Scheduler tick executed at: {time}”, DateTimeOffset.UtcNow); await DoWorkAsync(stoppingToken); } catch (Exception ex) { _logger.LogError(ex, “An error occurred executing the scheduled task.”); } } _logger.LogInformation(“Custom Scheduler Service is stopping.”); } private async Task DoWorkAsync(CancellationToken cancellationToken) { // Place your custom task logic here await Task.Delay(2000, cancellationToken); // Simulating work } } Use code with caution. Step 3: Register the Service

In your Program.cs file, register your new custom scheduler into the Dependency Injection (DI) container:

var builder = Host.CreateApplicationBuilder(args); builder.Services.AddHostedService(); var host = builder.Build(); host.Run(); Use code with caution. 3. Handling Scoped Dependencies

Most real-world tasks require database access via an Entity Framework Core DbContext. Because BackgroundService is registered as a Singleton, you cannot directly inject Scoped services into its constructor. Doing so causes a dependency inversion capture error.

To safely use scoped services, inject the IServiceProvider and create a scope manually inside your execution loop:

private readonly IServiceProvider _serviceProvider; public CustomScheduler(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } private async Task DoWorkAsync(CancellationToken cancellationToken) { using var scope = _serviceProvider.CreateScope(); var dbContext = scope.ServiceProvider.GetRequiredService(); // Process records securely within the scope var pendingTasks = await dbContext.Tasks.Where(t => !t.IsProcessed).ToListAsync(cancellationToken); // … process tasks } Use code with caution. 4. Production Considerations

Building a basic loop is simple, but production environments require resilience.

Graceful Shutdown: Always pass the stoppingToken to asynchronous methods (like HTTP requests or database queries). This ensures your application doesn’t kill tasks abruptly during a deployment.

Concurrency Control: If a task takes 20 minutes to run but your timer ticks every 15 minutes, tasks will overlap. The PeriodicTimer naturally prevents overlapping because it waits for the previous tick execution block to finish before waiting for the next period.

Distributed Environments: If you scale your application to multiple server instances, each instance will run the scheduler simultaneously. To prevent duplicate task execution, implement a distributed lock using tools like Redis or database-backed synchronization primitives. To tailor this architecture to your needs, tell me:

What is the exact task you want to schedule (e.g., database cleanups, third-party API syncing)?

Will this run on a single server or across multiple instances?

Do you require complex schedules like Cron expressions (e.g., “Every first Monday of the month”)?

I can provide the specific code adjustments for your scenario.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *