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

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM