zoukankan      html  css  js  c++  java
  • ASP.NET Core: BackgroundService停止(StopAsync)后无法重新启动(StartAsync)的问题

    这里的 BackgroundService 是指:

    Microsoft.Extensions.Hosting.BackgroundService

    1. 问题复现

    继承该BackgroundService,实现自己的MyService :

        public class MyService : BackgroundService
        {
            private CancellationTokenSource _CancelSource;
    
            public async Task StartAsync()
            {
                _CancelSource = new CancellationTokenSource();
                await base.StartAsync(_CancelSource.Token);
                Console.WriteLine("Start");
            }
    
            public async Task CancelAsync()
            {
                await base.StopAsync(_CancelSource.Token);
                Console.WriteLine("Stop");
            }
    
            protected override async Task ExecuteAsync(CancellationToken stoppingToken)
            {
                while (!stoppingToken.IsCancellationRequested)
                {
                    await Task.Delay(1000, stoppingToken).ContinueWith(tsk =>
                    {
                        Console.WriteLine(string.Format("{0} working...", DateTime.Now.ToString("mm:ss")));
                    });
                }
            }
        }

    实例化后可以启动运行,然后停止。但是再次调用该实例的 StartAsync()就启动不了了。

    当然,从 BackgroundService 实现的接口(IHostedService)来看,可能这个类本身就没打算让你手动控制启停。

    2. 原因

    一句话概括就是

    作为函数输入参数的 CancellationToken 并没有用到

    这也就是难怪网上的教程都是直接使用 CancellationToken.None 作为输入参数的原因。

    下面是详细分析

    直接贴下 BackgroundService 的 源码

    // Copyright (c) .NET Foundation. All rights reserved.
    // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
    
    using System;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace Microsoft.Extensions.Hosting
    {
        /// <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();
    
            /// <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)
            {
                // 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();
            }
        }
    }

    可以看到上面 StartAsync 函数调用 ExecuteAsync 时给它赋的参数直接是一个内部的只读变量,你在外部调用 StartAsync 给它输入的参数根本就没有用到。

    结果就是,调用 StopAsync 之后,_stoppingCts 触发了Cancel请求,那么 _stoppingCts.IsCancellationRequested 就变成了 true,因为是只读的,所以再次调用StartAsync 来启动,进入 ExecuteAsync  之后 while判断直接就是false跳出了。

    3. 解决办法

    方法一:跳过StartAsync、StopAsync ,直接调用 ExecuteAsync ;

    方法二:仿照官方的 BackgroundService,实现 IHostedService 接口,自己写一个 BackgroundService

    方法三:使用 BackgroundWorker

  • 相关阅读:
    Apache Ant 1.9.1 版发布
    Apache Subversion 1.8.0rc2 发布
    GNU Gatekeeper 3.3 发布,网关守护管理
    Jekyll 1.0 发布,Ruby 的静态网站生成器
    R语言 3.0.1 源码已经提交到 Github
    SymmetricDS 3.4.0 发布,数据同步和复制
    beego 0.6.0 版本发布,Go 应用框架
    Doxygen 1.8.4 发布,文档生成工具
    SunshineCRM 20130518发布,附带更新说明
    Semplice Linux 4 发布,轻量级发行版
  • 原文地址:https://www.cnblogs.com/IUpdatable/p/11947179.html
Copyright © 2011-2022 走看看