ASP.NET Core: BackgroundService中止(StopAsync)後沒法從新啓動(StartAsync)的問題

 

這裏的 BackgroundService 是指:git

Microsoft.Extensions.Hosting.BackgroundServicegithub

 

1. 問題復現

繼承該BackgroundService,實現本身的MyService :app

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()就啓動不了了。async

固然,從 BackgroundService 實現的接口(IHostedService)來看,可能這個類自己就沒打算讓你手動控制啓停。ide

2. 緣由

一句話歸納就是函數

做爲函數輸入參數的 CancellationToken 並無用到this

這也就是難怪網上的教程都是直接使用 CancellationToken.None 做爲輸入參數的緣由。spa

 

下面是詳細分析code

直接貼下 BackgroundService 的 源碼orm

// 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

相關文章
相關標籤/搜索