Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Background task of writing to the database by timer

How to write to the database on a timer in the background. For example, check mail and add new letters to the database. In the example, I simplified the code just before writing to the database.

The class names from the example in Microsoft. The recording class itself:

namespace EmailNews.Services
{

internal interface IScopedProcessingService
{
    void DoWork();
}

internal class ScopedProcessingService : IScopedProcessingService
{
    private readonly ApplicationDbContext _context;
    public ScopedProcessingService(ApplicationDbContext context)
    {
        _context = context;
    }

    public void DoWork()
    {
        Mail mail = new Mail();
        mail.Date = DateTime.Now;
        mail.Note = "lala";
        mail.Tema = "lala";
        mail.Email = "lala";
        _context.Add(mail);
        _context.SaveChangesAsync();
    }
}
}

Timer class:

namespace EmailNews.Services
{
#region snippet1
internal class TimedHostedService : IHostedService, IDisposable
{
    private readonly ILogger _logger;
    private Timer _timer;

    public TimedHostedService(IServiceProvider services, ILogger<TimedHostedService> logger)
    {
        Services = services;
        _logger = logger;
    }
    public IServiceProvider Services { get; }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is starting.");

        _timer = new Timer(DoWork, null, TimeSpan.Zero,
            TimeSpan.FromMinutes(1));

        return Task.CompletedTask;
    }

    private void DoWork(object state)
    {
        using (var scope = Services.CreateScope())
        {
            var scopedProcessingService =
                scope.ServiceProvider
                    .GetRequiredService<IScopedProcessingService>();

            scopedProcessingService.DoWork();
        }
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is stopping.");

        _timer?.Change(Timeout.Infinite, 0);

        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _timer?.Dispose();
    }
}
#endregion
}

Startup:

        services.AddHostedService<TimedHostedService>();
        services.AddScoped<IScopedProcessingService, ScopedProcessingService>();

It seems everything is done as in the example, but nothing is added to the database, which is not so?

like image 477
blakcat Avatar asked Nov 02 '18 10:11

blakcat


People also ask

What is background service in net core?

BackgroundService is a base class for implementing a long running IHostedService. ExecuteAsync(CancellationToken) is called to run the background service. The implementation returns a Task that represents the entire lifetime of the background service.

How do I run a background task in Python?

We can configure a new daemon thread to execute a custom function that will perform a long-running task, such as monitor a resource or data. For example we might define a new function named background_task(). Then, we can configure a new threading. Thread instance to execute this function via the “target” argument.

What is ASP NET MVC background worker?

Introduction. ASP.NET Boilerplate provides background jobs and workers that are used to execute some tasks in the background threads of an application.


1 Answers

This is a rather interesting question, that boils down to "How do you correctly handle an async timer callback?"

The immediate problem is that SaveChangesAsync isn't getting awaited. The DbContext almost certainly gets disposed before SaveChangesAsync has a chance to run. To await it, DoWork must become an async Task method (never async void) :

internal interface IScheduledTask
{
    Task DoWorkAsync();
}

internal class MailTask : IScheduledTask
{
    private readonly ApplicationDbContext _context;
    public MailTask(ApplicationDbContext context)
    {
        _context = context;
    }

    public async Task DoWorkAsync()
    {
        var mail = new Mail 
                   { Date = DateTime.Now,
                     Note = "lala",
                     Tema = "lala",
                     Email = "lala" };
        _context.Add(mail);
        await _context.SaveChangesAsync();
    }
}

The problem now is how to call DoWorkAsync from the timer callback. If we just call it without awaiting, we'll get the same problem we had in the first place. A timer callback can't handle methods that return Task. We can't make it async void either, because this would result in the same problem - the method will return before any async operation has a chance to finish.

David Fowler explains how to properly handle asynchronous timer callbacks in the Timer Callbacks section of his Async Guidance article :

private readonly Timer _timer;
private readonly HttpClient _client;

public Pinger(HttpClient client)
{
    _client = new HttpClient();
    _timer = new Timer(Heartbeat, null, 1000, 1000);
}

public void Heartbeat(object state)
{
    // Discard the result
    _ = DoAsyncPing();
}

private async Task DoAsyncPing()
{
    await _client.GetAsync("http://mybackend/api/ping");
}

The actual method should be async Task but the returned task only has to be assigned, not awaited, in order for it to work properly.

Applying this to the question leads to something like this :

public Task StartAsync(CancellationToken cancellationToken)
{
    ...
    _timer = new Timer(HeartBeat, null, TimeSpan.Zero,
        TimeSpan.FromMinutes(1));

    return Task.CompletedTask;
}

private void Heartbeat(object state)
{
    _ = DoWorkAsync();
}


private async Task DoWorkAsync()
{
    using (var scope = Services.CreateScope())
    {
        var schedTask = scope.ServiceProvider
                             .GetRequiredService<IScheduledTask>();

        await schedTask.DoWorkAsync();
    }
}

David Fowler explains why async void is ALWAY BAD in ASP.NET Core - it's not only that async actions won't be awaited, exceptions will crash the application.

He also explains why we can't use Timer(async state=>DoWorkAsync(state)) - that's an async void delegate.

like image 187
Panagiotis Kanavos Avatar answered Oct 02 '22 09:10

Panagiotis Kanavos