.net core 基于 ihostedservice 实现定时任务

intro

从 .net core 2.0 开始,开始引入 ihostedservice,可以通过 ihostedservice 来实现后台任务,但是只能在 webhost 的基础上使用。从 .net core 2.1 开始微软引入通用主机(generic host),使得我们可以在不使用 web 的情况下,也可以使用 ihostedservice 来实现 定时任务/windows服务/后台任务,并且引入了一个 backgroundservice 抽象类来更方便的创建后台任务。

ihostedservice

ihostedservice 后台任务的执行与应用程序(就此而言,为主机或微服务)的生存期相协调。 当应用程序启动时注册任务,当应用程序关闭时,有机会执行某些正常操作或清理。

始终可以启动后台线程来运行任何任务,而无需使用 ihostedservice。 不同之处就在于应用的关闭时间,此时会直接终止线程,而没有机会执行正常的清理操作。

当注册 ihostedservice 时,.net core 会在应用程序启动和停止期间分别调用 ihostedservice 类型的 startasync()stopasync() 方法。 具体而言,即在服务器已启动并已触发 iapplicationlifetime.applicationstarted 后调用 start。

namespace microsoft.extensions.hosting
{
    //
    // summary:
    //     defines methods for objects that are managed by the host.
    public interface ihostedservice
    {
        //
        // summary:
        // triggered when the application host is ready to start the service.
        task startasync(cancellationtoken cancellationtoken);
        //
        // summary:
        // triggered when the application host is performing a graceful shutdown.
        task stopasync(cancellationtoken cancellationtoken);
    }
}

可以从头开始创建自定义托管服务类并实现 ihostedservice,因为在使用 .net core 2.0 时需执行这些操作。

但是,由于大多数后台任务在取消令牌管理和其他典型操作方面都有类似的需求,因此 .net core 2.1 有一个非常方便且可以从中进行派生的抽象基类,backgroundservice 定义如下:

// copyright (c) .net foundation. licensed under the apache license, version 2.0.
/// <summary>
/// base class for implementing a long running <see cref="ihostedservice"/>.
/// </summary>
public abstract class backgroundservice : ihostedservice, idisposable
{
    private task _executingtask;
    private readonly cancellationtokensource _stoppingcts =
                                                   new cancellationtokensource();

    protected abstract task executeasync(cancellationtoken stoppingtoken);

    public virtual task startasync(cancellationtoken cancellationtoken)
    {
        // store the task we're executing
        _executingtask = executeasync(_stoppingcts.token);

        // if the task is completed then return it,
        // this will bubble cancellation and failure to the caller
        if (_executingtask.iscompleted)
        {
            return _executingtask;
        }

        // otherwise it's running
        return task.completedtask;
    }

    public virtual async task stopasync(cancellationtoken cancellationtoken)
    {
        // stop called without start
        if (_executingtask == null)
        {
            return;
        }

        try
        {
            // signal cancellation to the executing method
            _stoppingcts.cancel();
        }
        finally
        {
            // wait until the task completes or the stop token triggers
            await task.whenany(_executingtask, task.delay(timeout.infinite, cancellationtoken));
        }

    }

    public virtual void dispose()
    {
        _stoppingcts.cancel();
    }
}

实现一个的后台定时任务

基于上面的信息,我们可以基于 ihostedservice 实现一个简单的后台定时任务服务,

public abstract class scheduedservice : ihostedservice, idisposable
{
    private readonly timer _timer;
    private readonly timespan _period;
    protected readonly ilogger logger;

    protected scheduedservice(timespan period, ilogger logger)
    {
        logger = logger;
        _period = period;
        _timer = new timer(execute, null, timeout.infinite, 0);
    }

    public void execute(object state = null)
    {
        try
        {
            logger.loginformation("begin execute service");
            executeasync().wait();
        }
        catch (exception ex)
        {
            logger.logerror(ex, "execute exception");
        }
        finally
        {
            logger.loginformation("execute finished");
        }
    }

    protected abstract task executeasync();

    public virtual void dispose()
    {
        _timer?.dispose();
    }

    public task startasync(cancellationtoken cancellationtoken)
    {
        logger.loginformation("service is starting.");
        _timer.change(timespan.fromseconds(securityhelper.random.next(10)), _period);
        return task.completedtask;
    }

    public task stopasync(cancellationtoken cancellationtoken)
    {
        logger.loginformation("service is stopping.");

        _timer?.change(timeout.infinite, 0);

        return task.completedtask;
    }
}

基于上面这个基于timer实现的后台定时任务类实现一个定时任务:

public class removeoverduedreservtaionservice : scheduedservice
{
    public removeoverduedreservtaionservice(ilogger<removeoverduedreservtaionservice> logger) : base(timespan.fromdays(1), logger)
    { }

    protected override task executeasync()
    {
        return dependencyresolver.current.tryinvokeserviceasync<iefrepository<reservationdbcontext, reservation>>(reservationrepo =>
        {
            return reservationrepo.deleteasync(reservation => reservation.reservationstatus == 0 && (reservation.reservationfordate < datetime.today.adddays(-3)));
        });
    }
}

这个类实现的是每天执行一次,删除三天前状态为待审核的预约,完整实现代码:https://github.com/weihanli/activityreservation/blob/dev/activityreservation.helper/services/removeoverduedreservtaionservice.cs

在程序启动的时候注册服务:

services.addhostedservice<removeoverduedreservtaionservice>();

执行日志:

通过日志可以看到我们的定时任务确实是每天执行一次,这样我们的定时任务就算是简单的完成了。

reference

  • https://github.com/weihanli/activityreservation