ASP.NET Core IHostedService手动启动/停止/暂停(?)

44
我想在ASP.NET Core中实现一个可以按时间间隔重复执行的IHostedService实例,可以在需要时停止并启动。我的理解是IHostedService是由框架在应用程序启动时启动的。
但是,我希望能够手动地启动/停止服务,可能使用UI上的开/关切换。理想情况下,“关闭”状态将会处理当前正在运行的服务,并且“打开”状态将创建一个新实例。
我已经阅读了MS文档:https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1
我的初步想法是获取正在运行的服务实例,然后调用公共StopAsync(CancellationToken token)方法。但是,当涉及到我应该传递哪个token时,我有点卡住了,StartAsync(CancellationToken cancellationToken)方法也是如此。
你有任何关于应该如何完成这项任务的想法吗,或者这么做甚至可行吗?我的方法是否违反了ASP.NET Core中托管服务的预期设计? 编辑 7.27.2018

经过更多的研究(也就是实际阅读文档:D),似乎托管服务的StartAsync/StopAsync方法确实应该与应用程序的生命周期相一致。似乎无法将注册的IHostedServices添加到DI容器中以便于注入其他类。

因此,我认为我的初始想法行不通。目前,我使用带有配置依赖项(IOptions<T>)注册了我的服务。在托管服务处理时,它将检查配置以查看是否应该继续运行,否则它将只等待(而不是停止或处置托管服务)。

除非听到其他建议,否则我很快会将此标记为我的答案。


服务的目标是什么?听起来你想要一个Windows服务。 - Camilo Terevinto
由于您将把取消令牌传递给这些函数,它们很可能会查看 IsCancellationRequested 和 CanBeCancelled 属性。请查看 https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken?view=netframework-4.7.2 - Hozikimaru
1
你能分享一下关于"IOptions<T>"的解决方案吗?我想在运行时更改BackgroundService的延迟。 - dataCore
3个回答

25
对于StopAsync(CancellationToken token),您可以传递new System.Threading.CancellationToken()。在public CancellationToken(bool canceled)的定义中,canceled表示令牌的状态。对于您的场景,无需指定canceled,因为您想要停止服务。 您可以按以下步骤操作:
  1. 创建IHostedService
   public class RecureHostedService : IHostedService, IDisposable
 {
private readonly ILogger _log;
private Timer _timer;
public RecureHostedService(ILogger<RecureHostedService> log)
{
    _log = log;
}

public void Dispose()
{
    _timer.Dispose();
}

public Task StartAsync(CancellationToken cancellationToken)
{
    _log.LogInformation("RecureHostedService is Starting");
    _timer = new Timer(DoWork,null,TimeSpan.Zero, TimeSpan.FromSeconds(5));
    return Task.CompletedTask;
}

public Task StopAsync(CancellationToken cancellationToken)
{
    _log.LogInformation("RecureHostedService is Stopping");
    _timer?.Change(Timeout.Infinite, 0);
    return Task.CompletedTask;
}
private void DoWork(object state)
{
    _log.LogInformation("Timed Background Service is working.");
}
}
  • 注册 IHostedService

  •     services.AddSingleton<IHostedService, RecureHostedService>();
    
  • 启动和停止服务

  •  public class HomeController : Controller {
     private readonly RecureHostedService _recureHostedService;
     public HomeController(IHostedService hostedService)
     {
         _recureHostedService = hostedService as RecureHostedService;
     }
     public IActionResult About()
     {
         ViewData["Message"] = "Your application description page.";
         _recureHostedService.StopAsync(new System.Threading.CancellationToken());
         return View();
     }
    
     public IActionResult Contact()
     {
         ViewData["Message"] = "Your contact page.";
         _recureHostedService.StartAsync(new System.Threading.CancellationToken());
         return View();
     } }
    

    2
    我能在另一个IHostedService中自动停止IHostedService吗?我的意思是自动地,而不是手动地。 - Thiện Sinh
    5
    如何防止“自启动”? - marrrschine
    4
    当你有多个服务时,如何指定要使用哪个服务来处理 HomeController(IHostedService hostedService) 中的部分? - p.deman
    1
    @p.deman,你也可以这样做private readonly IHostedService _recureHostedService = null; public ServiceController(IEnumerable<IHostedService> hostedService) { _recureHostedService = hostedService.Where(c => c.GetType().Name == nameof(RecureHostedService)).FirstOrDefault(); } - dotsa
    你应该/可以做的是以下内容。 如果我假设你想要Windows服务集成,你可以链接cancellationTokens内部静态CancellationTokenSource CancellationToken; protected async override Task ExecuteAsync(CancellationToken stoppingToken) { CancellationToken = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken);另一个服务可以“取消”你的服务,然后你可以重新运行AddHostedService。 - user2410689
    显示剩余4条评论

    5
    使用 Blazor Server,你可以按照以下方式启动和停止后台服务。ASP.NET Core MVC 或 Razor 的原理相同。
    首先,实现 IHostService 接口。
    public class BackService : IHostedService, IDisposable
    {
        private readonly ILogger _log;
        private Timer _timer;
        public bool isRunning { get; set; }
        public BackService(ILogger<V2rayFlowBackService> log)
        {
            _log = log;
        }
    
        public void Dispose()
        {
            _timer.Dispose();
        }
    
        public Task StartAsync(CancellationToken cancellationToken)
        {
            _log.LogInformation($"begin {DateTime.Now}");
            _timer = new Timer(DoWorkAsync, null, TimeSpan.Zero, TimeSpan.FromSeconds(10));
            return Task.CompletedTask;
        }
    
        public Task StopAsync(CancellationToken cancellationToken)
        {
            isRunning = false;
            _log.LogInformation($"{DateTime.Now} BackService is Stopping");
            _timer?.Change(Timeout.Infinite, 0);
            return Task.CompletedTask;
        }
        private void DoWorkAsync(object state)
        {
            _log.LogInformation($"Timed Background Service is working.  {DateTime.Now}");
            try
            {
                isRunning = true;
                // dosometing you want
            }
            catch (Exception ex)
            {
                isRunning = false;
                _log.LogInformation("Error {0}", ex.Message);
                throw ex;
    
            }
        }
    }
    

    在 Startup.cs 中的注册服务

    public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<BackService>();
            services.AddHostedService(sp => sp.GetRequiredService<BackService>());
        }
    

    在Blazor组件中注入后台服务
    public class IndexBase:ComponentBase
    {
        [Inject]
        BackService BackService { set; get; }
    
        protected override void OnInitialized()
        {
            if (BackService.isRunning)
            {
                BackService.StopAsync(new System.Threading.CancellationToken());
            }
            base.OnInitialized();
        }
        public void on()
        {
            if (!BackService.isRunning)
            {
                BackService.StartAsync(new System.Threading.CancellationToken());
            }
    
        }
        public void off()
        {
            if (BackService.isRunning)
            {
                BackService.StopAsync(new System.Threading.CancellationToken());
            }
    
        }
    }
    

    @page "/"
    @inherits IndexBase
    <h1>Hello, world!</h1>
    
    Welcome to your new app.
    
    <button @onclick="on">Start</button>
    <button @onclick="off">Stop</button>
    

    参考链接


    这对于 Windows 服务是否有效? - Cenk
    @Cenk 我没有试过,你可以试试看。 - SmallProgram
    我应该在Blazor Server中创建工作服务以便从Razer页面启动/停止吗?你是如何实现的? - Cenk

    4
    你应该从自己的接口继承你的HostedService,并将你的服务注册为Singleton,但是以不同的方式:
    首先,使用AddHostedService泛型方法注册你的服务。
            services.AddHostedService<TimerHostedService>();
    

    在你的类中添加一个名为Instance的公共静态字段,它保存类实例的引用,并在构造函数中设置其值!

    然后在 ConfigureServices 中加入一个工厂方法,将你的服务注册为单例模式并返回静态实例字段!

    以下是示例代码:(在你的 HostedService.cs: 文件中)

    public interface ITimerHostedService : IHostedService
    {
    
    }
    
    public class TimerHostedService : ITimerHostedService
    {
        private static TimerHostedService _instance;
    
        public static TimerHostedService Instance => _instance;
    
        public TimerHostedService(ILogger<TimerHostedService> logger)
        {
            if(_instance == null)
            {
                _instance = this;
            }
        }
    }
    

    下面是在 Startup.cs 中将服务注册为单例的代码:

            services.AddHostedService<TimerHostedService>();
    
            services.AddSingleton<ITimerHostedService, TimerHostedService>(serviceProvider =>
            {
                return TimerHostedService.Instance;
            });
    

    下面是在你的控制器(Controller)中手动启动/停止托管服务的代码:

    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;
        private readonly ITimerHostedService _hostedService;
    
        public HomeController(ILogger<HomeController> logger, ITimerHostedService hostedService)
        {
            _logger = logger;
            _hostedService = hostedService;
        }
    
        public async Task<IActionResult> Start()
        {
            await _hostedService.StartAsync(default);
            
            return Ok();
        }
    
        public async Task<IActionResult> Stop()
        {
            await _hostedService.StopAsync(default);
            
            return Ok();
        }
    }
    

    愉快地编码吧!

    享受美好时光 :X


    这是我唯一正确运行的代码。使用 .Net Core 6。 - Markus S
    我应该在Blazor Server中创建工作服务,以便可以从Razor页面启动/停止吗? - Cenk

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接