• 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

  • 相关阅读:
    腾讯2016春招安全岗笔试题解析
    AlgorithmVisualizer
    agentzh --春哥--调试专家
    大话Java性能优化 BOOK
    《Linux内核分析》-----张超
    ROS中Mangle解析
    shell中trap捕获信号
    虚拟化技术性能总结:Zones, KVM, Xen
    Dtrace on Mac OS X
    linux内核学习-建议路线
  • 原文地址:https://www.cnblogs.com/IUpdatable/p/11947179.html
Copyright © 2020-2023  润新知