Recurring tasks in .NET Core Console App [closed]

2020-06-17 06:13发布

The short version is - how do I (in 2017 having .NET Core 1.1 released) run recurring tasks in .NET Core console app with Microsoft's dependency injection?

The long story and code snippets.

The goal

I have 4 (and will have more) services each of which has say one method that performs certain isolated task and interacts with database. Specifically, I have one service that removes old data from database, one service that "caches" data (run through data in DB and stores aggregated results in other table), one that generates random data and so on... The idea is to run those tasks with different intervals, say every 20 seconds, or once a day.

Current dirty implementation

I have a service that for each task has a while(true) loop that spawns a service with DI, runs task, Thread.Sleep(interval), checks exit condition (class-wide flag) and continue/break. Most of the methods are async.

Current problems

First of all, I know that while(true) and Thread.Sleep(interval) is a huge hack (technical debt). Second, I have memory leak and I feel like the way I spawn/dispose service has something to do with it. Finally, the code encounters random errors and all exceptions are swallowed. I am not able to catch them in any known to me way.

The solution I want

I want to achieve the goal, but if possible

  • avoid large frameworks, like Hangfire and Quartz. I feel like I do not need a fraction of what they can.
  • keep app a console app. Although this is a "helper" for ASP project, I want it to be a console app and be able to run it like dotnet daemons.dll and it does its job indefinitely until I Ctrl+C it. It also should not grow in memory and CPU/threads consumption. It is OK if it eats up considerable amount of RAM as long as it is a stable number.
  • use Microsoft DI. That's how all my services are built.
  • keep my services async. I may reuse them in different scenarios including unit testing.

Code snippets

Example of how I run recurring task:

private async Task RunCacheServiceAsync()
{
    // Make sure the service is set to run.
    _status[ServiceManagerServices.Cache] = true;

    _logger.LogInformation(LoggingEvents.ServiceManager.AsInt(), "Cache service started.");

    while (true)
    {
        var metrics = await _serviceProvider
            .GetRequiredService<DataContext>()
            .Metrics
            .ToListAsync();

        // Run the tasks
        var tasks = metrics
            .Select(
                (metric) =>
                Task.Factory.StartNew(async () =>
                {
                    await _serviceProvider
                        .GetRequiredService<ICacheService>()
                        .CacheMetric(metric);
                })
            );

        // Wait completion of all tasks
        await Task.WhenAll(tasks.ToArray());

        // Wait
        Thread.Sleep(_intervals[ServiceManagerServices.Cache]);

        // Check exit condition
        if (!_status[ServiceManagerServices.Cache])
        {
            break;
        }
    }
}

Example of the task that I need to be periodic:

public async Task CleanDataPointsAsync()
{
    var toTimestamp = DateTime.Now - _maxAge;

    // remove data points of all types
    _context.RemoveRange(
        _context.NumericDataPoints.Where(dp => dp.Timestamp < toTimestamp)
    );

    await _context.SaveChangesAsync();

    _logger.LogInformation(LoggingEvents.Clean.AsInt(), "Cleaned old data.");
}

The question

Please, suggest a general approach I can take to achieve the goal considering constraints. Any feedback on posted code and reasoning is appreciated!

0条回答
登录 后发表回答