【问题标题】:How to stop and resume worker service?如何停止和恢复工作人员服务?
【发布时间】:2020-04-29 07:13:17
【问题描述】:

在 WPF 应用程序中,我已配置托管服务以在后台执行特定活动(关注 this article)。 这是在 App.xaml.cs 中配置托管服务的方式。

public App()
        {
            var environmentName = Environment.GetEnvironmentVariable("HEALTHBOOSTER_ENVIRONMENT") ?? "Development";
            IConfigurationRoot configuration = SetupConfiguration(environmentName);
            ConfigureLogger(configuration);
            _host = Host.CreateDefaultBuilder()
                .UseSerilog()
                .ConfigureServices((hostContext, services) =>
                {
                    services.AddHostedService<Worker>()
                    .AddOptions()
                    .AddSingleton<IMailSender, MailSender>()
                    .AddSingleton<ITimeTracker, TimeTracker>()
                    .AddSingleton<NotificationViewModel, NotificationViewModel>()
                    .AddTransient<NotificationWindow, NotificationWindow>()
                    .Configure<AppSettings>(configuration.GetSection("AppSettings"));
                }).Build();

            AssemblyLoadContext.Default.Unloading += Default_Unloading;

            Console.CancelKeyPress += Console_CancelKeyPress;

            SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
        }

并在启动时开始

/// <summary>
    /// Handles statup event
    /// </summary>
    /// <param name="e"></param>
    protected override async void OnStartup(StartupEventArgs e)
    {
        try
        {
            Log.Debug("Starting the application");
            await _host.StartAsync(_cancellationTokenSource.Token);
            base.OnStartup(e);
        }
        catch (Exception ex)
        {
            Log.Error(ex, "Failed to start application");
            await StopAsync();
        }
    }

现在我想在系统进入睡眠状态时停止托管服务,并在系统恢复时重新启动服务。我试过这个

/// <summary>
    /// Handles system suspend and resume events
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private async void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
    {
        switch (e.Mode)
        {
            case PowerModes.Resume:
                Log.Warning("System is resuming. Restarting the host");
                try
                {
                    _cancellationTokenSource = new CancellationTokenSource();
                    await _host.StartAsync(_cancellationTokenSource.Token);
                }
                catch (Exception ex)
                {
                    Log.Error(ex, $"{ex.Message}");
                }
                break;

            case PowerModes.Suspend:
                Log.Warning("System is suspending. Canceling the activity");
                _cancellationTokenSource.Cancel();
                await _host.StopAsync(_cancellationTokenSource.Token);
                break;
        }
    }

停止主机工作正常但是当主机重新启动时,我得到'System.OperationCanceledException'。根据我的理解,托管服务的生命周期独立于应用程序的生命周期。我的理解错了吗?

这个问题-ASP.NET Core IHostedService manual start/stop/pause(?) 类似,但答案是根据配置暂停并重新启动服务,这似乎是一种黑客行为,所以我正在寻找一种标准方法。

有什么想法吗?

【问题讨论】:

  • 将调用Host.CreateDefaultBuilder()并初始化主机的代码移动到OnStartup方法,以便在每次要重新启动它时创建一个新主机。你会发现一个例子here
  • 我相信主机被设计为只运行一次。不过,您也许可以停止和启动您的后台服务。 Worker 有自己的启动和停止(大部分)独立于主机启动和停止。

标签: c# wpf .net-core task ihostedservice


【解决方案1】:

正如 cmets 中的 Stephen Cleary 所述,worker 可以独立于主机启动和停止,但理想情况下主机应该处理 worker 的生命周期。
但是由于 .NET Core 3 中的一个现有错误,传递给 IHostedService StartAsync 方法的取消令牌不会传播给工作人员 ExecuteAsync 方法。我已经为此创建了一个问题,可以在此处找到详细信息 - https://github.com/dotnet/extensions/issues/3218

该错误的修复 (https://github.com/dotnet/extensions/pull/2823) 将成为 .NET 5 的一部分,因此正如问题 (https://github.com/dotnet/extensions/issues/3218#issuecomment-622503957) 中所建议的那样,我必须创建自己的类来模仿框架的 BackGroundService 类,而这个类将直接从IHostedService 继承并将取消令牌传播给工作人员。

BackGroundService类自定义实现在这里-

/// <summary>
    /// Base class for implementing a long running <see cref="IHostedService"/>.
    /// </summary>
    public abstract class BGService : IHostedService, IDisposable
    {
        private Task _executingTask;
        private CancellationTokenSource _stoppingCts;

        /// <summary>
        /// This method is called when the <see cref="IHostedService"/> starts. The implementation should return a task that represents
        /// the lifetime of the long running operation(s) being performed.
        /// </summary>
        /// <param name="stoppingToken">Triggered when <see cref="IHostedService.StopAsync(CancellationToken)"/> is called.</param>
        /// <returns>A <see cref="Task"/> that represents the long running operations.</returns>
        protected abstract Task ExecuteAsync(CancellationToken stoppingToken);

        /// <summary>
        /// Triggered when the application host is ready to start the service.
        /// </summary>
        /// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
        public virtual Task StartAsync(CancellationToken cancellationToken)
        {
            // Create linked token to allow cancelling executing task from provided token
            _stoppingCts = CancellationTokenSource.CreateLinkedTokenSource(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;
        }

        /// <summary>
        /// Triggered when the application host is performing a graceful shutdown.
        /// </summary>
        /// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param>
        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();
        }
    }

以及系统挂起和恢复时分别停止和启动worker的逻辑

/// <summary>
        /// Handles system suspend and resume events
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private async void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
        {
            var workers = _host.Services.GetServices<IHostedService>();
            Log.Information($"Found IHostedService instances - {workers.ToCSV()}");
            switch (e.Mode)
            {
                case PowerModes.Resume:
                    Log.Warning("System is resuming. Restarting the workers");
                    try
                    {
                        _cancellationTokenSource = new CancellationTokenSource();
                        foreach (var worker in workers)
                        {
                            await worker.StartAsync(_cancellationTokenSource.Token);
                        }
                    }
                    catch (Exception ex)
                    {
                        Log.Error(ex, $"{ex.Message}");
                    }
                    break;

                case PowerModes.Suspend:
                    Log.Warning("System is suspending. Stopping the workers");
                    _cancellationTokenSource.Cancel();
                    try
                    {
                        foreach (var worker in workers)
                        {
                            await worker.StopAsync(_cancellationTokenSource.Token);
                        }
                    }
                    catch (Exception ex)
                    {
                        Log.Error(ex, $"{ex.Message}");
                    }
                    break;
            }
        }

请注意@davidfowl 建议这不是受支持的功能 (https://github.com/dotnet/extensions/issues/3218#issuecomment-623280990),但我没有遇到这种方法的任何问题,并且相信这也应该是受支持的用例。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-02-20
    • 2021-02-05
    • 1970-01-01
    • 2023-03-09
    • 2020-02-05
    • 2021-12-23
    • 1970-01-01
    相关资源
    最近更新 更多