Skip to content

Bits of .NET

Daily micro-tips for C#, SQL, performance, and scalable backend engineering.

  • Asp.Net Core
  • C#
  • SQL
  • JavaScript
  • CSS
  • About
  • ErcanOPAK.com
  • No Access
  • Privacy Policy
Asp.Net Core

.NET Core: Use IHostedService for Background Tasks

- 22.03.26 - ErcanOPAK

⏰ Long-Running Tasks in ASP.NET Core

Need scheduled jobs? Background processing? Email queue? IHostedService runs tasks in background without blocking requests.

Basic Background Service

public class EmailQueueService : BackgroundService
{
    private readonly ILogger _logger;
    private readonly IServiceProvider _services;

    public EmailQueueService(
        ILogger logger,
        IServiceProvider services)
    {
        _logger = logger;
        _services = services;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Email Queue Service started");

        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                // Create scope for scoped services
                using var scope = _services.CreateScope();
                var emailService = scope.ServiceProvider
                    .GetRequiredService();

                // Process emails
                await emailService.ProcessQueueAsync();

                // Wait 30 seconds before next run
                await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error processing email queue");
            }
        }

        _logger.LogInformation("Email Queue Service stopped");
    }
}

// Program.cs - Register service
builder.Services.AddHostedService();

🎯 Scheduled Task Example

public class DailyReportService : BackgroundService
{
    private readonly ILogger _logger;
    private readonly IServiceProvider _services;

    public DailyReportService(
        ILogger logger,
        IServiceProvider services)
    {
        _logger = logger;
        _services = services;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            // Calculate time until next 9 AM
            var now = DateTime.Now;
            var nextRun = DateTime.Today.AddHours(9);
            
            if (now > nextRun)
            {
                nextRun = nextRun.AddDays(1);
            }

            var delay = nextRun - now;
            
            _logger.LogInformation($"Next report at {nextRun}");
            
            // Wait until 9 AM
            await Task.Delay(delay, stoppingToken);

            // Generate report
            using var scope = _services.CreateScope();
            var reportService = scope.ServiceProvider
                .GetRequiredService();
            
            await reportService.GenerateDailyReportAsync();
        }
    }
}

IHostedService vs BackgroundService

IHostedService

// Manual implementation
public class MyService : IHostedService
{
    public Task StartAsync(
        CancellationToken ct)
    {
        // Start logic
        return Task.CompletedTask;
    }

    public Task StopAsync(
        CancellationToken ct)
    {
        // Cleanup
        return Task.CompletedTask;
    }
}

Use when: Need full control

BackgroundService

// Easier implementation
public class MyService 
    : BackgroundService
{
    protected override async Task
        ExecuteAsync(
            CancellationToken ct)
    {
        // Long-running work
        while (!ct.IsCancellationRequested)
        {
            await DoWorkAsync();
            await Task.Delay(1000, ct);
        }
    }
}

Use when: Long-running loops

Real-World Use Cases

📧 Email Queue Processor

  • Checks database for pending emails every 30 seconds
  • Sends emails in background
  • Marks as sent/failed in database
  • Doesn’t block web requests

🗑️ Cleanup Service

  • Runs every night at 2 AM
  • Deletes old temp files
  • Archives old database records
  • Clears expired cache entries

📊 Data Sync Service

  • Polls external API every 5 minutes
  • Syncs data to local database
  • Updates cache
  • Notifies connected clients via SignalR

💡 Best Practices

  • Use scopes for DI: Hosted services are singletons, create scopes for scoped services
  • Handle exceptions: Wrap work in try-catch, log errors
  • Respect cancellation: Check stoppingToken.IsCancellationRequested
  • Graceful shutdown: Clean up resources in StopAsync
  • Don’t block startup: Long initialization? Do it async in ExecuteAsync

⚠️ Important Notes

  • Not for heavy work: For CPU-intensive tasks, use separate worker service
  • No guaranteed execution: App restart = service stops
  • Consider Hangfire/Quartz: For complex scheduling, use dedicated libraries
  • Health checks: Add health check endpoint to monitor service status

“Moved email sending to background service. API responses instant now. Emails send reliably in background. Added retry logic. Zero impact on user experience. Should’ve done this years ago.”

— Backend Developer

Related posts:

Environment-Specific Config Overrides

.NET Core: Handling Errors Gracefully with Middleware

Use IHttpClientFactory or Face Socket Exhaustion

Post Views: 6

Post navigation

Git: Use git cherry-pick to Copy Specific Commits Between Branches
.NET Core: Add Health Checks to Monitor Application Status

Leave a Reply Cancel reply

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

April 2026
M T W T F S S
 12345
6789101112
13141516171819
20212223242526
27282930  
« Mar    

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (950)
  • How to add default value for Entity Framework migrations for DateTime and Bool (858)
  • Get the First and Last Word from a String or Sentence in SQL (836)
  • How to select distinct rows in a datatable in C# (805)
  • How to make theater mode the default for Youtube (751)
  • Add Constraint to SQL Table to ensure email contains @ (578)
  • How to enable, disable and check if Service Broker is enabled on a database in SQL Server (564)
  • Average of all values in a column that are not zero in SQL (531)
  • How to use Map Mode for Vertical Scroll Mode in Visual Studio (489)
  • Find numbers with more than two decimal places in SQL (447)

Recent Posts

  • C#: Use Init-Only Setters for Immutable Objects After Construction
  • C#: Use Expression-Bodied Members for Concise Single-Line Methods
  • C#: Enable Nullable Reference Types to Eliminate Null Reference Exceptions
  • C#: Use Record Types for Immutable Data Objects
  • SQL: Use CTEs for Readable Complex Queries
  • SQL: Use Window Functions for Advanced Analytical Queries
  • .NET Core: Use Background Services for Long-Running Tasks
  • .NET Core: Use Minimal APIs for Lightweight HTTP Services
  • Git: Use Cherry-Pick to Apply Specific Commits Across Branches
  • Git: Use Interactive Rebase to Clean Up Commit History Before Merge

Most Viewed Posts

  • Get the User Name and Domain Name from an Email Address in SQL (950)
  • How to add default value for Entity Framework migrations for DateTime and Bool (858)
  • Get the First and Last Word from a String or Sentence in SQL (836)
  • How to select distinct rows in a datatable in C# (805)
  • How to make theater mode the default for Youtube (751)

Recent Posts

  • C#: Use Init-Only Setters for Immutable Objects After Construction
  • C#: Use Expression-Bodied Members for Concise Single-Line Methods
  • C#: Enable Nullable Reference Types to Eliminate Null Reference Exceptions
  • C#: Use Record Types for Immutable Data Objects
  • SQL: Use CTEs for Readable Complex Queries

Social

  • ErcanOPAK.com
  • GoodReads
  • LetterBoxD
  • Linkedin
  • The Blog
  • Twitter
© 2026 Bits of .NET | Built with Xblog Plus free WordPress theme by wpthemespace.com