之前在公司的一個項目中需要用到定時程序,當時使用的是aspnet core提供的IHostedService接口來實現后台定時程序,具體的示例可去官網查看。現在的dotnet core中默認封裝了實現IHostedService接口的基類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();
}
}
}
// 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(); } } }
根據BackgroundService源碼,我們只要實現該類的抽象方法ExecuteAsync即可。
可以有兩種實現方式來做定時程序,第一種就是實現一個Timer:

using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; using System.IO; using System.Threading; using System.Threading.Tasks; namespace DemoOne.Models { public class TimedBackgroundService : BackgroundService { private readonly ILogger _logger; private Timer _timer; public TimedBackgroundService(ILogger<TimedBackgroundService> logger) { _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5)); _logger.LogInformation("周六!"); return Task.CompletedTask; //Console.WriteLine("MyServiceA is starting."); //stoppingToken.Register(() => File.Create($"E:\\dotnetCore\\Practice\\Practice\\{DateTime.Now.Millisecond}.txt")); //while (!stoppingToken.IsCancellationRequested) //{ // Console.WriteLine("MyServiceA 開始執行"); // await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); // Console.WriteLine("繼續執行"); //} //Console.WriteLine("MyServiceA background task is stopping."); } private void DoWork(object state) { _logger.LogInformation($"Hello World! - {DateTime.Now}"); } public override void Dispose() { base.Dispose(); _timer?.Dispose(); } } }
我們看看StartAsync的源碼。上面的實現方式會直接返回一個已完成的Task,這樣就會直接運行StartAsync方法的if判斷,那么如果我們不走if呢?那么就應該由StartAsync方法返回一個已完成的Task.
第二個即是:

using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; using System.IO; using System.Threading; using System.Threading.Tasks; namespace DemoOne.Models { public class TimedBackgroundService : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { Console.WriteLine("MyServiceA is starting."); stoppingToken.Register(() => File.Create($"E:\\dotnetCore\\Practice\\Practice\\{DateTime.Now.Millisecond}.txt")); while (!stoppingToken.IsCancellationRequested) { Console.WriteLine("MyServiceA 開始執行"); await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); Console.WriteLine("繼續執行"); } Console.WriteLine("MyServiceA background task is stopping."); } public override void Dispose() { base.Dispose(); } } }
最后我們將實現了BackgroundService的類注入到DI即可:
services.AddHostedService<TimedBackgroundService>();
dotnet core的Microsoft.Extensions.Hosting 組件中,充斥着類似IHostedService接口中定義的方法:StartAsync、StopAsync方法。我們注入的HostedService服務會在WebHost類中通過GetRequiredService獲取到注入的定時服務。隨后執行StartAsync方法開始執行。建議看看Hosting組件源碼,會有很多的收獲。