【问题标题】:Scheduled task on .Net Core.Net Core 上的计划任务
【发布时间】:2021-07-10 20:24:39
【问题描述】:

我需要使用 .Net 将几个计划任务从寡妇服务器的 Scheduler 任务转换为独立应用程序。

过去我在 .Net 框架 4.x 上使用过 Quartz,但由于基于不同调度程序的多个长时间运行的任务存在一些小问题。

现在我正在使用 .Net 5,我想知道是否有一种新的方法来安排任务,例如工作服务,或者使用 Quartz.Net 会更好、更灵活。

由于我需要运行长时间的任务,从 30 秒到 2 小时,我需要使用 System.Threading.Timer 创建一个定时后台任务

代码应该如下:

    public Task StartAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Timed Hosted Service running.");

        _timer = new Timer(DoSomething, null, TimeSpan.Zero, 
            TimeSpan.FromHours(24));

        return Task.CompletedTask;
    }

它应该每 24 小时调用一次 DoSomething。

我的疑问是:

  • 当我第一次跑步时,它什么时候开始工作并计算 24 小时 申请?
  • 我怎么能说任务必须在 一天中的特定时间,例如午夜?
  • Worker Service 是否适合管理计划任务?

【问题讨论】:

  • HangFire、Quartz.Net、Caravel。所有这些都不仅带有计时器,而且带有重试、优先级、链接、仪表板的队列。调度任务不仅仅是一个计时器。您将如何检查您的作业是否运行?错误呢?如果您想按需运行作业怎么办? Windows 的任务计划程序提供了许多您在不知不觉中依赖的功能。
  • @PanagiotisKanavos 你是对的,我完全同意。
  • 工作服务只不过是一个带有 BackgroundService 的控制台应用程序,您可以通过将托管服务添加到通用主机来自己创建它。它本身不提供任何调度功能。您可以使用该 BackgroundService 来托管 HangFire、Coravel 或 Quartz.NET 等调度库。如果您基于 Web SDK 的辅助服务(或创建一个基本的 ASP.NET Core 项目并添加一个 BackgroundService),您可以在同一个应用程序上托管您最喜欢的调度库的 Web 仪表板
  • Hangfire's Getting Started ASP.NET Core 教程展示了如何添加库、仪表板和使用数据库作为作业的后备存储。 Hangfire 有自己的 BackgroundService 来运行这些作业。
  • @PanagiotisKanavos Hangfire 似乎比 Quartz.Net 更先进,不是吗?

标签: c# .net-core scheduled-tasks quartz.net


【解决方案1】:

当我第一次运行应用程序时,它什么时候开始工作并计算 24 小时?

是的。设置断点并启动您的应用程序。你会看到它的发射速度有多快。

Worker Service 是否适合管理计划任务?

是的。

我怎么能说任务必须在一天中的特定时间运行,例如午夜?

让我们看看这段代码:

public sealed class MyTimedBackgroundService : BackgroundService
{

    private static int SecondsUntilMidnight()
    {
        return (int)(DateTime.Today.AddDays(1.0) - DateTime.Now).TotalSeconds;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        var countdown = SecondsUntilMidnight();

        while (!stoppingToken.IsCancellationRequested)
        {
            if (countdown-- <= 0)
            {
                try
                {
                    await OnTimerFiredAsync(stoppingToken);
                }
                catch(Exception ex)
                {
                    // TODO: log exception
                }
                finally
                {
                    countdown = SecondsUntilMidnight();
                }
            }
            await Task.Delay(1000, stoppingToken);
        }
    }

    private async Task OnTimerFiredAsync(CancellationToken stoppingToken)
    {
        // do your work here
        Debug.WriteLine("Simulating heavy I/O bound work");
        await Task.Delay(2000, stoppingToken);
    }
}

这根本不使用System.Threading.Timer,因为您担心计时器由于某些边界而实际上永远不会触发。有些人对此很偏执。我从来没有遇到过这种情况。我经常使用Timer 来完成这类工作。

它将计算直到午夜的秒数,然后循环直到到达那里。

这是一个不可重入的计时器,由于处理延迟的业务逻辑,会有轻微的时间滑移。

这是另一个使用System.Threading.Timer的例子:

public sealed class MyTimedBackgroundService : IHostedService
{
    private Timer _t;

    private static int MilliSecondsUntilMidnight()
    {
        return (int)(DateTime.Today.AddDays(1.0) - DateTime.Now).TotalMilliseconds;
    }

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        // set up a timer to be non-reentrant
        _t = new Timer(async _ => await OnTimerFiredAsync(cancellationToken),
            null, MilliSecondsUntilMidnight(), Timeout.Infinite);
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _t?.Dispose();
        return Task.CompletedTask;
    }

    private async Task OnTimerFiredAsync(CancellationToken cancellationToken)
    {
        try
        {
            // do your work here
            Debug.WriteLine("Simulating heavy I/O bound work");
            await Task.Delay(2000, cancellationToken);
        }
        finally
        {
            // set timer to fire off again
            _t?.Change(MilliSecondsUntilMidnight(), Timeout.Infinite);
        }
    }
}

(此代码未经测试,可能存在一些拼写/语法错误)

这是一个不可重入的计时器,这意味着如果它当前正在处理数据,您可以保证它不会再次触发。

它将计算直到午夜的毫秒数,然后根据该计算设置一个计时器。

这个想法来自from Microsoft

这两个例子都可以这样注入:

services.AddHostedService<MyTimedBackgroundService>();

云原生警告:

请记住,由于这些示例是您的应用程序的本地示例,因此如果您的应用程序在运行多个实例的情况下横向扩展,您将运行两个或更多个计时器,就在单独的进程。只是一个善意的提醒。如果您的应用程序永远无法扩展,请忽略此警告。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-05-06
    • 2016-08-14
    • 1970-01-01
    • 2011-01-19
    • 1970-01-01
    • 2011-10-11
    • 2010-12-29
    相关资源
    最近更新 更多