【问题标题】:How to create task on startup and stop it on application stop?如何在启动时创建任务并在应用程序停止时停止它?
【发布时间】:2018-08-06 18:56:09
【问题描述】:

我正在使用带有 .net 核心的 mvc,我需要在启动时运行一个任务,并在应用程序停止时停止它。在 Startup.cs 我注册了应用程序启动和停止的事件。问题是,我不知道如何运行必须在启动时在特定类中运行的任务。任务如下所示:

public void PreventStatusChange()
    {
        while (forceStatusChange)
        {
            foreach (var ext in GetExtensions())
            {
                ext.Status = StatusType.Available;
            }
            Thread.Sleep(1000);
        }
    }

变量 forceStatusChange 在同一个类中声明,所以我在 Startup.cs 中看不到它。最好的方法是什么?

【问题讨论】:

标签: c# asp.net-mvc asp.net-core


【解决方案1】:

您需要创建一个实现IHostedService 的类。该接口只定义了两个方法,StartAsync 在应用程序启动时调用,StopAsync 在应用程序终止时调用。

您需要将其注册为托管服务:

services.AddHostedService<TimedHostedService>();

小心使用AddHostedService不要 AddSingleton。如果您使用AddSingleton,运行时将不知道在适当的时候调用 StartAsync 和 StopAsync。

文章Background tasks with hosted services in ASP.NET Core 展示了如何使用计时器实现服务:

internal class TimedHostedService : IHostedService, IDisposable
{
    private readonly ILogger _logger;
    private Timer _timer;

    public TimedHostedService(ILogger<TimedHostedService> logger)
    {
        _logger = logger;
    }

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

        _timer = new Timer(DoWork, null, TimeSpan.Zero, 
            TimeSpan.FromSeconds(10));

        return Task.CompletedTask;
    }

    private void DoWork(object state)
    {
        _logger.LogInformation("Timed Background Service is working.");
    }

    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();
    }
}

这段代码没有什么特别有趣的地方 - 只需在调用 StartAsync 时启动一个计时器,然后在 StopAsync 上停止它

取消长时间运行的任务

当运行时需要回收或停止时,它会在所有托管服务上调用StopAsync 方法,等待它们优雅地完成,然后警告它们立即取消。片刻之后,它将继续并终止应用程序或回收它。

cancellationToken 参数用于指示服务应立即停止。通常,这意味着您必须编写自己的代码来检查它,警告您自己的任务终止,等待所有任务完成等,类似于代码shown in this article

不过,这几乎是样板文件,这就是为什么 BackgroundService 类可用于创建只需要实现 ExecuteAsync(CancellationToken) 的类。启动和停止该任务由BackgroundService 提供,例如:

public class PollingService : BackgroundService
{

    private readonly ILogger _logger;

    public PollingService(ILogger<PollingService> logger)
    {
        _logger = logger;
    }

    protected async override Task ExecuteAsync(
        CancellationToken cancellationToken)
    {

        while (!cancellationToken.IsCancellationRequested)
        {

            try
            {
                await DoSomething(cancellationToken);
                await Task.Delay(1000,cancellationToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, 
                   $"Error occurred executing {nameof(workItem)}.");
            }
        }

        _logger.LogInformation("Queued Hosted Service is stopping.");
    }        
}

在这种情况下,Task.Delay() 本身将在运行时引发取消令牌后立即被取消。 DoSomething() 本身应该以检查取消令牌的方式实现,例如将其传递给任何接受它作为参数的异步方法,在每个循环上测试 IsCancellationRequested 属性并退出它。

例如:

    protected async override Task ExecuteAsync(
        CancellationToken cancellationToken)
    {

        while (!cancellationToken.IsCancellationRequested)
        {
            try
            {
                foreach (var ext in GetExtensions())
                {
                    //Oops, time to cancel
                    if(cancellationToken.IsCancellationRequested)
                    {
                        break;
                    }
                    //Otherwise, keep working
                    ext.Status = StatusType.Available;
                }
                await Task.Delay(1000,cancellationToken);
            }
            catch (Exception ex)
            {
                ...
            }
        }

        _logger.LogInformation("Hosted Service is stopping.");
    }        

【讨论】:

  • 感谢您提供非常丰富的答案。有没有办法用 AddSingleton 做到这一点?因为我需要该服务作为单身人士。
  • @Jamil 为什么?我已经解释了你不能的一个原因,链接的文章解释了更多。查看 Phil Haack 的The dangers of implementing recurring tasks in ASP.NET。 Scott Hanselman 解释了various methods to properly execute recurring tasks。托管服务是 .NET Core 中的一种新方法
  • @Jamil 您是否想将数据发布到服务?检查文档中的QueuedHostedService 示例。它不是注入托管服务,而是创建并注入一个单例队列,该队列可用于将数据发布到服务。这样客户就不需要知道服务是在运行还是在停止。
  • 不,此服务在后台运行并处理传入/传出呼叫并记录有关它们的数据,这就是我需要单例实例的原因。
  • @Jamil 这是过滤器和记录器的工作,而不是长期运行的任务。不过,您仍然不需要单例实例,除非您想将其作为依赖项注入
【解决方案2】:

你可以使用BackgroundService

public class LongRunningService : BackgroundService
{ 
    public LongRunningService()
    {
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested && forceStatusChange)
        {
            foreach (var ext in GetExtensions())
            {
                ext.Status = StatusType.Available;
            }

            await Task.Delay(1000, stoppingToken);
        }  
    }

    protected override async Task StopAsync (CancellationToken stoppingToken)
    {
        // Run your graceful clean-up actions
    }
}

并注册:

public void ConfigureServices(IServiceCollection services)
{
   ...
   services.AddSingleton<IHostedService, LongRunningService>();
   ...
}

【讨论】:

  • 问题是我的服务已经有了接口:.AddSingleton() 是否必须设置服务类型IHostedService?
  • @Jamil,你可以把它注入到BackgroundService的构造函数中,不过你的服务应该稍​​微修改一下
  • @AlexRiabov 这不是托管服务的工作方式。如果没有调用AddHostedService,这只是另一个单例。
  • @PanagiotisKanavos 不按这篇文章blogs.msdn.microsoft.com/cesardelatorre/2017/11/18/…
  • @AlexRiabov 那是我链接到的文章,你误解了它显示的内容。它展示了如何在您自己的代码中复制相同的功能。 DI 基础架构仍然需要调用StartAsyncStopAsync,如果它不知道 IHostedService 接口就无法做到这一点
猜你喜欢
  • 2011-07-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多