在 ASP.NET Core 中,后台任务作为托管服务实现。 托管服务是一个类,具有实现
IHostedService
接口的后台任务逻辑。 本文提供了三个托管服务示例:
在计时器上运行的后台任务。
激活有
作用域的服务
的托管服务。 有作用域的服务可使用
依赖项注入 (DI)
。
按顺序运行的已排队后台任务。
辅助角色服务模板
ASP.NET Core 辅助角色服务模板可作为编写长期服务应用的起点。 通过辅助角色服务模板创建的应用将在其项目文件中指定 Worker SDK:
<Project Sdk="Microsoft.NET.Sdk.Worker">
要使用该模板作为编写托管服务应用的基础:
Visual Studio
Visual Studio for Mac
.NET Core CLI
在侧栏中的“.NET Core”下,选择“应用”。
在“ASP.NET Core”下,选择“辅助角色”。 选择“下一页”。
对于“目标框架”,选择“.NET Core 3.1”或更高版本 。 选择“下一页”。
在“项目名称”字段中提供名称。 选择“创建”。
将辅助角色服务 (worker
) 模板用于命令行界面中的 dotnet new 命令。 下面的示例中创建了名为 ContosoWorker
的辅助角色服务应用。 执行命令时会自动为 ContosoWorker
应用创建文件夹。
dotnet new worker -o ContosoWorker
基于辅助角色服务模板的应用使用 Microsoft.NET.Sdk.Worker
SDK,并且具有对 Microsoft.Extensions.Hosting 包的显式包引用。 有关示例,请参阅示例应用的项目文件 (BackgroundTasksSample.csproj
)。
对于使用 Microsoft.NET.Sdk.Web
SDK 的 Web 应用,通过共享框架隐式引用 Microsoft.Extensions.Hosting 包。 在应用的项目文件中不需要显式包引用。
IHostedService 接口
IHostedService 接口为主机托管的对象定义了两种方法:
StartAsync(CancellationToken)
StopAsync(CancellationToken)
StartAsync
StartAsync(CancellationToken) 包含用于启动后台任务的逻辑。 在以下操作之前调用 StartAsync
:
已配置应用的请求处理管道。
已启动服务器且已触发 IApplicationLifetime.ApplicationStarted。
StartAsync
应仅限于短期任务,因为托管服务是按顺序运行的,在 StartAsync
运行完成之前不会启动其他服务。
StopAsync
StopAsync(CancellationToken) 在主机执行正常关闭时触发。 StopAsync
包含结束后台任务的逻辑。 实现 IDisposable 和终结器(析构函数)以处置任何非托管资源。
默认情况下,取消令牌会有 30 秒超时,以指示关闭进程不再正常。 在令牌上请求取消时:
应中止应用正在执行的任何剩余后台操作。
StopAsync
中调用的任何方法都应及时返回。
但是,在请求取消后,将不会放弃任务,调用方会等待所有任务完成。
如果应用意外关闭(例如,应用的进程失败),则可能不会调用 StopAsync
。 因此,在 StopAsync
中执行的任何方法或操作都可能不会发生。
若要延长默认值为 30 秒的关闭超时值,请设置:
ShutdownTimeout(当使用通用主机时)。 有关详细信息,请参阅 ASP.NET Core 中的 .NET 通用主机。
使用 Web 主机时为关闭超时值主机配置设置。 有关详细信息,请参阅 ASP.NET Core Web 主机。
托管服务在应用启动时激活一次,在应用关闭时正常关闭。 如果在执行后台任务期间引发错误,即使未调用 StopAsync
,也应调用 Dispose
。
BackgroundService 基类
BackgroundService 是用于实现长时间运行的 IHostedService 的基类。
调用 ExecuteAsync(CancellationToken) 来运行后台服务。 实现返回一个 Task,其表示后台服务的整个生存期。 在 ExecuteAsync 变为异步(例如通过调用 await
)之前,不会启动任何其他服务。 避免在 ExecuteAsync
中执行长时间的阻塞初始化工作。 StopAsync(CancellationToken) 中的主机块等待完成 ExecuteAsync
。
调用 IHostedService.StopAsync 时,将触发取消令牌。 当激发取消令牌以便正常关闭服务时,ExecuteAsync
的实现应立即完成。 否则,服务将在关闭超时后不正常关闭。 有关更多信息,请参阅 IHostedService interface 部分。
有关详细信息,请参阅 BackgroundService 源代码。
计时的后台任务
定时后台任务使用 System.Threading.Timer 类。 计时器触发任务的 DoWork
方法。 在 StopAsync
上禁用计时器,并在 Dispose
上处置服务容器时处置计时器:
public class TimedHostedService : IHostedService, IDisposable
private int executionCount = 0;
private readonly ILogger<TimedHostedService> _logger;
private Timer? _timer = null;
public TimedHostedService(ILogger<TimedHostedService> logger)
_logger = logger;
public Task StartAsync(CancellationToken stoppingToken)
_logger.LogInformation("Timed Hosted Service running.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromSeconds(5));
return Task.CompletedTask;
private void DoWork(object? state)
var count = Interlocked.Increment(ref executionCount);
_logger.LogInformation(
"Timed Hosted Service is working. Count: {Count}", count);
public Task StopAsync(CancellationToken stoppingToken)
_logger.LogInformation("Timed Hosted Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
public void Dispose()
_timer?.Dispose();
Timer 不等待先前的 DoWork
执行完成,因此所介绍的方法可能并不适用于所有场景。 使用 Interlocked.Increment 以原子操作的形式将执行计数器递增,这可确保多个线程不会并行更新 executionCount
。
已使用 AddHostedService
扩展方法在 IHostBuilder.ConfigureServices
(Program.cs
) 中注册该服务:
services.AddHostedService<TimedHostedService>();
在后台任务中使用有作用域的服务
要在 BackgroundService 中使用有作用域的服务,请创建作用域。 默认情况下,不会为托管服务创建作用域。
作用域后台任务服务包含后台任务的逻辑。 如下示例中:
服务是异步的。 DoWork
方法返回 Task
。 出于演示目的,在 DoWork
方法中等待 10 秒的延迟。
ILogger 注入到服务中。
internal interface IScopedProcessingService
Task DoWork(CancellationToken stoppingToken);
internal class ScopedProcessingService : IScopedProcessingService
private int executionCount = 0;
private readonly ILogger _logger;
public ScopedProcessingService(ILogger<ScopedProcessingService> logger)
_logger = logger;
public async Task DoWork(CancellationToken stoppingToken)
while (!stoppingToken.IsCancellationRequested)
executionCount++;
_logger.LogInformation(
"Scoped Processing Service is working. Count: {Count}", executionCount);
await Task.Delay(10000, stoppingToken);
托管服务创建一个作用域来解决作用域后台任务服务以调用其 DoWork
方法。 DoWork
返回 ExecuteAsync
等待的 Task
:
public class ConsumeScopedServiceHostedService : BackgroundService
private readonly ILogger<ConsumeScopedServiceHostedService> _logger;
public ConsumeScopedServiceHostedService(IServiceProvider services,
ILogger<ConsumeScopedServiceHostedService> logger)
Services = services;
_logger = logger;
public IServiceProvider Services { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
_logger.LogInformation(
"Consume Scoped Service Hosted Service running.");
await DoWork(stoppingToken);
private async Task DoWork(CancellationToken stoppingToken)
_logger.LogInformation(
"Consume Scoped Service Hosted Service is working.");
using (var scope = Services.CreateScope())
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedProcessingService>();
await scopedProcessingService.DoWork(stoppingToken);
public override async Task StopAsync(CancellationToken stoppingToken)
_logger.LogInformation(
"Consume Scoped Service Hosted Service is stopping.");
await base.StopAsync(stoppingToken);
已在 IHostBuilder.ConfigureServices
(Program.cs
) 中注册这些服务。 已使用 AddHostedService
扩展方法注册托管服务:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
排队的后台任务
后台任务队列基于 .NET 4.x QueueBackgroundWorkItem:
public interface IBackgroundTaskQueue
ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem);
ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken);
public class BackgroundTaskQueue : IBackgroundTaskQueue
private readonly Channel<Func<CancellationToken, ValueTask>> _queue;
public BackgroundTaskQueue(int capacity)
// Capacity should be set based on the expected application load and
// number of concurrent threads accessing the queue.
// BoundedChannelFullMode.Wait will cause calls to WriteAsync() to return a task,
// which completes only when space became available. This leads to backpressure,
// in case too many publishers/calls start accumulating.
var options = new BoundedChannelOptions(capacity)
FullMode = BoundedChannelFullMode.Wait
_queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
public async ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem)
if (workItem == null)
throw new ArgumentNullException(nameof(workItem));
await _queue.Writer.WriteAsync(workItem);
public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken)
var workItem = await _queue.Reader.ReadAsync(cancellationToken);
return workItem;
在以下 QueueHostedService
示例中:
BackgroundProcessing
方法返回 ExecuteAsync
中等待的 Task
。
在 BackgroundProcessing
中,取消排队并执行队列中的后台任务。
服务在 StopAsync
中停止之前,将等待工作项。
public class QueuedHostedService : BackgroundService
private readonly ILogger<QueuedHostedService> _logger;
public QueuedHostedService(IBackgroundTaskQueue taskQueue,
ILogger<QueuedHostedService> logger)
TaskQueue = taskQueue;
_logger = logger;
public IBackgroundTaskQueue TaskQueue { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
_logger.LogInformation(
$"Queued Hosted Service is running.{Environment.NewLine}" +
$"{Environment.NewLine}Tap W to add a work item to the " +
$"background queue.{Environment.NewLine}");
await BackgroundProcessing(stoppingToken);
private async Task BackgroundProcessing(CancellationToken stoppingToken)
while (!stoppingToken.IsCancellationRequested)
var workItem =
await TaskQueue.DequeueAsync(stoppingToken);
await workItem(stoppingToken);
catch (Exception ex)
_logger.LogError(ex,
"Error occurred executing {WorkItem}.", nameof(workItem));
public override async Task StopAsync(CancellationToken stoppingToken)
_logger.LogInformation("Queued Hosted Service is stopping.");
await base.StopAsync(stoppingToken);
每当在输入设备上选择 w
键时,MonitorLoop
服务将处理托管服务的排队任务:
IBackgroundTaskQueue
注入到 MonitorLoop
服务中。
调用 IBackgroundTaskQueue.QueueBackgroundWorkItem
来将工作项排入队列。
工作项模拟长时间运行的后台任务:
- 将执行三次 5 秒的延迟 (
Task.Delay
)。
- 如果任务已取消,
try-catch
语句将捕获 OperationCanceledException。
private readonly IBackgroundTaskQueue _taskQueue;
private readonly ILogger _logger;
private readonly CancellationToken _cancellationToken;
public MonitorLoop(IBackgroundTaskQueue taskQueue,
ILogger<MonitorLoop> logger,
IHostApplicationLifetime applicationLifetime)
_taskQueue = taskQueue;
_logger = logger;
_cancellationToken = applicationLifetime.ApplicationStopping;
public void StartMonitorLoop()
_logger.LogInformation("MonitorAsync Loop is starting.");
// Run a console user input loop in a background thread
Task.Run(async () => await MonitorAsync());
private async ValueTask MonitorAsync()
while (!_cancellationToken.IsCancellationRequested)
var keyStroke = Console.ReadKey();
if (keyStroke.Key == ConsoleKey.W)
// Enqueue a background work item
await _taskQueue.QueueBackgroundWorkItemAsync(BuildWorkItem);
private async ValueTask BuildWorkItem(CancellationToken token)
// Simulate three 5-second tasks to complete
// for each enqueued work item
int delayLoop = 0;
var guid = Guid.NewGuid().ToString();
_logger.LogInformation("Queued Background Task {Guid} is starting.", guid);
while (!token.IsCancellationRequested && delayLoop < 3)
await Task.Delay(TimeSpan.FromSeconds(5), token);
catch (OperationCanceledException)
// Prevent throwing if the Delay is cancelled
delayLoop++;
_logger.LogInformation("Queued Background Task {Guid} is running. "
+ "{DelayLoop}/3", guid, delayLoop);
if (delayLoop == 3)
_logger.LogInformation("Queued Background Task {Guid} is complete.", guid);
_logger.LogInformation("Queued Background Task {Guid} was cancelled.", guid);
已在 IHostBuilder.ConfigureServices
(Program.cs
) 中注册这些服务。 已使用 AddHostedService
扩展方法注册托管服务:
services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue>(ctx =>
if (!int.TryParse(hostContext.Configuration["QueueCapacity"], out var queueCapacity))
queueCapacity = 100;
return new BackgroundTaskQueue(queueCapacity);
已在 Program.cs
中启动 MonitorLoop
:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
异步定时后台任务
以下代码将创建异步定时后台任务:
namespace TimedBackgroundTasks;
public class TimedHostedService : BackgroundService
private readonly ILogger<TimedHostedService> _logger;
private int _executionCount;
public TimedHostedService(ILogger<TimedHostedService> logger)
_logger = logger;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
_logger.LogInformation("Timed Hosted Service running.");
// When the timer should have no due-time, then do the work once now.
DoWork();
using PeriodicTimer timer = new(TimeSpan.FromSeconds(1));
while (await timer.WaitForNextTickAsync(stoppingToken))
DoWork();
catch (OperationCanceledException)
_logger.LogInformation("Timed Hosted Service is stopping.");
// Could also be a async method, that can be awaited in ExecuteAsync above
private void DoWork()
int count = Interlocked.Increment(ref _executionCount);
_logger.LogInformation("Timed Hosted Service is working. Count: {Count}", count);
本机 AOT
辅助角色服务模板支持带有 --aot
标志的 .NET 本机预先 (AOT):
Visual Studio
.NET Core CLI
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>
+ <PublishAot>true</PublishAot>
<UserSecretsId>dotnet-WorkerWithAot-e94b2</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0-preview.4.23259.5" />
</ItemGroup>
</Project>
- GitHub 上的后台服务单元测试。
- 查看或下载示例代码(如何下载)
- 使用 IHostedService 和 BackgroundService 类在微服务中实现后台任务
- 在 Azure 应用服务中使用 WebJobs 运行后台任务
- Timer
在 ASP.NET Core 中,后台任务作为托管服务实现。 托管服务是一个类,具有实现 IHostedService 接口的后台任务逻辑。 本文提供了三个托管服务示例:
- 在计时器上运行的后台任务。
- 激活有作用域的服务的托管服务。 有作用域的服务可使用依赖项注入 (DI)。
- 按顺序运行的已排队后台任务。
辅助角色服务模板
ASP.NET Core 辅助角色服务模板可作为编写长期服务应用的起点。 通过辅助角色服务模板创建的应用将在其项目文件中指定 Worker SDK:
<Project Sdk="Microsoft.NET.Sdk.Worker">
要使用该模板作为编写托管服务应用的基础:
Visual Studio
Visual Studio for Mac
.NET Core CLI
- 在侧栏中的“.NET Core”下,选择“应用”。
- 在“ASP.NET Core”下,选择“辅助角色”。 选择“下一页”。
- 对于“目标框架”,选择“.NET Core 3.1”或更高版本 。 选择“下一页”。
- 在“项目名称”字段中提供名称。 选择“创建”。
将辅助角色服务 (worker
) 模板用于命令行界面中的 dotnet new 命令。 下面的示例中创建了名为 ContosoWorker
的辅助角色服务应用。 执行命令时会自动为 ContosoWorker
应用创建文件夹。
dotnet new worker -o ContosoWorker
基于辅助角色服务模板的应用使用 Microsoft.NET.Sdk.Worker
SDK,并且具有对 Microsoft.Extensions.Hosting 包的显式包引用。 有关示例,请参阅示例应用的项目文件 (BackgroundTasksSample.csproj
)。
对于使用 Microsoft.NET.Sdk.Web
SDK 的 Web 应用,通过共享框架隐式引用 Microsoft.Extensions.Hosting 包。 在应用的项目文件中不需要显式包引用。
IHostedService 接口
IHostedService 接口为主机托管的对象定义了两种方法:
- StartAsync(CancellationToken)
- StopAsync(CancellationToken)
StartAsync
StartAsync(CancellationToken) 包含用于启动后台任务的逻辑。 在以下操作之前调用 StartAsync
:
- 已配置应用的请求处理管道。
- 已启动服务器且已触发 IApplicationLifetime.ApplicationStarted。
StartAsync
应仅限于短期任务,因为托管服务是按顺序运行的,在 StartAsync
运行完成之前不会启动其他服务。
StopAsync
- StopAsync(CancellationToken) 在主机执行正常关闭时触发。
StopAsync
包含结束后台任务的逻辑。 实现 IDisposable 和终结器(析构函数)以处置任何非托管资源。
默认情况下,取消令牌会有 30 秒超时,以指示关闭进程不再正常。 在令牌上请求取消时:
- 应中止应用正在执行的任何剩余后台操作。
StopAsync
中调用的任何方法都应及时返回。
但是,在请求取消后,将不会放弃任务,调用方会等待所有任务完成。
如果应用意外关闭(例如,应用的进程失败),则可能不会调用 StopAsync
。 因此,在 StopAsync
中执行的任何方法或操作都可能不会发生。
若要延长默认值为 30 秒的关闭超时值,请设置:
- ShutdownTimeout(当使用通用主机时)。 有关详细信息,请参阅 ASP.NET Core 中的 .NET 通用主机。
- 使用 Web 主机时为关闭超时值主机配置设置。 有关详细信息,请参阅 ASP.NET Core Web 主机。
托管服务在应用启动时激活一次,在应用关闭时正常关闭。 如果在执行后台任务期间引发错误,即使未调用 StopAsync
,也应调用 Dispose
。
BackgroundService 基类
BackgroundService 是用于实现长时间运行的 IHostedService 的基类。
调用 ExecuteAsync(CancellationToken) 来运行后台服务。 实现返回一个 Task,其表示后台服务的整个生存期。 在 ExecuteAsync 变为异步(例如通过调用 await
)之前,不会启动任何其他服务。 避免在 ExecuteAsync
中执行长时间的阻塞初始化工作。 StopAsync(CancellationToken) 中的主机块等待完成 ExecuteAsync
。
调用 IHostedService.StopAsync 时,将触发取消令牌。 当激发取消令牌以便正常关闭服务时,ExecuteAsync
的实现应立即完成。 否则,服务将在关闭超时后不正常关闭。 有关更多信息,请参阅 IHostedService interface 部分。
有关详细信息,请参阅 BackgroundService 源代码。
计时的后台任务
定时后台任务使用 System.Threading.Timer 类。 计时器触发任务的 DoWork
方法。 在 StopAsync
上禁用计时器,并在 Dispose
上处置服务容器时处置计时器:
public class TimedHostedService : IHostedService, IDisposable
private int executionCount = 0;
private readonly ILogger<TimedHostedService> _logger;
private Timer? _timer = null;
public TimedHostedService(ILogger<TimedHostedService> logger)
_logger = logger;
public Task StartAsync(CancellationToken stoppingToken)
_logger.LogInformation("Timed Hosted Service running.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromSeconds(5));
return Task.CompletedTask;
private void DoWork(object? state)
var count = Interlocked.Increment(ref executionCount);
_logger.LogInformation(
"Timed Hosted Service is working. Count: {Count}", count);
public Task StopAsync(CancellationToken stoppingToken)
_logger.LogInformation("Timed Hosted Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
public void Dispose()
_timer?.Dispose();
Timer 不等待先前的 DoWork
执行完成,因此所介绍的方法可能并不适用于所有场景。 使用 Interlocked.Increment 以原子操作的形式将执行计数器递增,这可确保多个线程不会并行更新 executionCount
。
已使用 AddHostedService
扩展方法在 IHostBuilder.ConfigureServices
(Program.cs
) 中注册该服务:
services.AddHostedService<TimedHostedService>();
在后台任务中使用有作用域的服务
要在 BackgroundService 中使用有作用域的服务,请创建作用域。 默认情况下,不会为托管服务创建作用域。
作用域后台任务服务包含后台任务的逻辑。 如下示例中:
- 服务是异步的。
DoWork
方法返回 Task
。 出于演示目的,在 DoWork
方法中等待 10 秒的延迟。
- ILogger 注入到服务中。
internal interface IScopedProcessingService
Task DoWork(CancellationToken stoppingToken);
internal class ScopedProcessingService : IScopedProcessingService
private int executionCount = 0;
private readonly ILogger _logger;
public ScopedProcessingService(ILogger<ScopedProcessingService> logger)
_logger = logger;
public async Task DoWork(CancellationToken stoppingToken)
while (!stoppingToken.IsCancellationRequested)
executionCount++;
_logger.LogInformation(
"Scoped Processing Service is working. Count: {Count}", executionCount);
await Task.Delay(10000, stoppingToken);
托管服务创建一个作用域来解决作用域后台任务服务以调用其 DoWork
方法。 DoWork
返回 ExecuteAsync
等待的 Task
:
public class ConsumeScopedServiceHostedService : BackgroundService
private readonly ILogger<ConsumeScopedServiceHostedService> _logger;
public ConsumeScopedServiceHostedService(IServiceProvider services,
ILogger<ConsumeScopedServiceHostedService> logger)
Services = services;
_logger = logger;
public IServiceProvider Services { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
_logger.LogInformation(
"Consume Scoped Service Hosted Service running.");
await DoWork(stoppingToken);
private async Task DoWork(CancellationToken stoppingToken)
_logger.LogInformation(
"Consume Scoped Service Hosted Service is working.");
using (var scope = Services.CreateScope())
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedProcessingService>();
await scopedProcessingService.DoWork(stoppingToken);
public override async Task StopAsync(CancellationToken stoppingToken)
_logger.LogInformation(
"Consume Scoped Service Hosted Service is stopping.");
await base.StopAsync(stoppingToken);
已在 IHostBuilder.ConfigureServices
(Program.cs
) 中注册这些服务。 已使用 AddHostedService
扩展方法注册托管服务:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
排队的后台任务
后台任务队列基于 .NET 4.x QueueBackgroundWorkItem:
public interface IBackgroundTaskQueue
ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem);
ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken);
public class BackgroundTaskQueue : IBackgroundTaskQueue
private readonly Channel<Func<CancellationToken, ValueTask>> _queue;
public BackgroundTaskQueue(int capacity)
// Capacity should be set based on the expected application load and
// number of concurrent threads accessing the queue.
// BoundedChannelFullMode.Wait will cause calls to WriteAsync() to return a task,
// which completes only when space became available. This leads to backpressure,
// in case too many publishers/calls start accumulating.
var options = new BoundedChannelOptions(capacity)
FullMode = BoundedChannelFullMode.Wait
_queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
public async ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem)
if (workItem == null)
throw new ArgumentNullException(nameof(workItem));
await _queue.Writer.WriteAsync(workItem);
public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken)
var workItem = await _queue.Reader.ReadAsync(cancellationToken);
return workItem;
在以下 QueueHostedService
示例中:
BackgroundProcessing
方法返回 ExecuteAsync
中等待的 Task
。
- 在
BackgroundProcessing
中,取消排队并执行队列中的后台任务。
- 服务在
StopAsync
中停止之前,将等待工作项。
public class QueuedHostedService : BackgroundService
private readonly ILogger<QueuedHostedService> _logger;
public QueuedHostedService(IBackgroundTaskQueue taskQueue,
ILogger<QueuedHostedService> logger)
TaskQueue = taskQueue;
_logger = logger;
public IBackgroundTaskQueue TaskQueue { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
_logger.LogInformation(
$"Queued Hosted Service is running.{Environment.NewLine}" +
$"{Environment.NewLine}Tap W to add a work item to the " +
$"background queue.{Environment.NewLine}");
await BackgroundProcessing(stoppingToken);
private async Task BackgroundProcessing(CancellationToken stoppingToken)
while (!stoppingToken.IsCancellationRequested)
var workItem =
await TaskQueue.DequeueAsync(stoppingToken);
await workItem(stoppingToken);
catch (Exception ex)
_logger.LogError(ex,
"Error occurred executing {WorkItem}.", nameof(workItem));
public override async Task StopAsync(CancellationToken stoppingToken)
_logger.LogInformation("Queued Hosted Service is stopping.");
await base.StopAsync(stoppingToken);
每当在输入设备上选择 w
键时,MonitorLoop
服务将处理托管服务的排队任务:
IBackgroundTaskQueue
注入到 MonitorLoop
服务中。
- 调用
IBackgroundTaskQueue.QueueBackgroundWorkItem
来将工作项排入队列。
- 工作项模拟长时间运行的后台任务:
- 将执行三次 5 秒的延迟 (
Task.Delay
)。
- 如果任务已取消,
try-catch
语句将捕获 OperationCanceledException。
private readonly IBackgroundTaskQueue _taskQueue;
private readonly ILogger _logger;
private readonly CancellationToken _cancellationToken;
public MonitorLoop(IBackgroundTaskQueue taskQueue,
ILogger<MonitorLoop> logger,
IHostApplicationLifetime applicationLifetime)
_taskQueue = taskQueue;
_logger = logger;
_cancellationToken = applicationLifetime.ApplicationStopping;
public void StartMonitorLoop()
_logger.LogInformation("MonitorAsync Loop is starting.");
// Run a console user input loop in a background thread
Task.Run(async () => await MonitorAsync());
private async ValueTask MonitorAsync()
while (!_cancellationToken.IsCancellationRequested)
var keyStroke = Console.ReadKey();
if (keyStroke.Key == ConsoleKey.W)
// Enqueue a background work item
await _taskQueue.QueueBackgroundWorkItemAsync(BuildWorkItem);
private async ValueTask BuildWorkItem(CancellationToken token)
// Simulate three 5-second tasks to complete
// for each enqueued work item
int delayLoop = 0;
var guid = Guid.NewGuid().ToString();
_logger.LogInformation("Queued Background Task {Guid} is starting.", guid);
while (!token.IsCancellationRequested && delayLoop < 3)
await Task.Delay(TimeSpan.FromSeconds(5), token);
catch (OperationCanceledException)
// Prevent throwing if the Delay is cancelled
delayLoop++;
_logger.LogInformation("Queued Background Task {Guid} is running. "
+ "{DelayLoop}/3", guid, delayLoop);
if (delayLoop == 3)
_logger.LogInformation("Queued Background Task {Guid} is complete.", guid);
_logger.LogInformation("Queued Background Task {Guid} was cancelled.", guid);
已在 IHostBuilder.ConfigureServices
(Program.cs
) 中注册这些服务。 已使用 AddHostedService
扩展方法注册托管服务:
services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue>(ctx =>
if (!int.TryParse(hostContext.Configuration["QueueCapacity"], out var queueCapacity))
queueCapacity = 100;
return new BackgroundTaskQueue(queueCapacity);
已在 Program.cs
中启动 MonitorLoop
:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
异步定时后台任务
以下代码将创建异步定时后台任务:
namespace TimedBackgroundTasks;
public class TimedHostedService : BackgroundService
private readonly ILogger<TimedHostedService> _logger;
private int _executionCount;
public TimedHostedService(ILogger<TimedHostedService> logger)
_logger = logger;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
_logger.LogInformation("Timed Hosted Service running.");
// When the timer should have no due-time, then do the work once now.
DoWork();
using PeriodicTimer timer = new(TimeSpan.FromSeconds(1));
while (await timer.WaitForNextTickAsync(stoppingToken))
DoWork();
catch (OperationCanceledException)
_logger.LogInformation("Timed Hosted Service is stopping.");
// Could also be a async method, that can be awaited in ExecuteAsync above
private void DoWork()
int count = Interlocked.Increment(ref _executionCount);
_logger.LogInformation("Timed Hosted Service is working. Count: {Count}", count);
- GitHub 上的后台服务单元测试。
- 查看或下载示例代码(如何下载)
- 使用 IHostedService 和 BackgroundService 类在微服务中实现后台任务
- 在 Azure 应用服务中使用 WebJobs 运行后台任务
- Timer
在 ASP.NET Core 中,后台任务作为托管服务实现。 托管服务是一个类,具有实现 IHostedService 接口的后台任务逻辑。 本文提供了三个托管服务示例:
- 在计时器上运行的后台任务。
- 激活有作用域的服务的托管服务。 有作用域的服务可使用依赖项注入 (DI)。
- 按顺序运行的已排队后台任务。
查看或下载示例代码(如何下载)
辅助角色服务模板
ASP.NET Core 辅助角色服务模板可作为编写长期服务应用的起点。 通过辅助角色服务模板创建的应用将在其项目文件中指定 Worker SDK:
<Project Sdk="Microsoft.NET.Sdk.Worker">
要使用该模板作为编写托管服务应用的基础:
Visual Studio
Visual Studio for Mac
.NET Core CLI
- 在侧栏中的“.NET Core”下,选择“应用”。
- 在“ASP.NET Core”下,选择“辅助角色”。 选择“下一页”。
- 对于“目标框架”,选择“.NET Core 3.0”或更高版本。 选择“下一页”。
- 在“项目名称”字段中提供名称。 选择“创建”。
将辅助角色服务 (worker
) 模板用于命令行界面中的 dotnet new 命令。 下面的示例中创建了名为 ContosoWorker
的辅助角色服务应用。 执行命令时会自动为 ContosoWorker
应用创建文件夹。
dotnet new worker -o ContosoWorker
基于辅助角色服务模板的应用使用 Microsoft.NET.Sdk.Worker
SDK,并且具有对 Microsoft.Extensions.Hosting 包的显式包引用。 有关示例,请参阅示例应用的项目文件 (BackgroundTasksSample.csproj
)。
对于使用 Microsoft.NET.Sdk.Web
SDK 的 Web 应用,通过共享框架隐式引用 Microsoft.Extensions.Hosting 包。 在应用的项目文件中不需要显式包引用。
IHostedService 接口
IHostedService 接口为主机托管的对象定义了两种方法:
- StartAsync(CancellationToken)
- StopAsync(CancellationToken)
StartAsync
StartAsync
包含启动后台任务的逻辑。 在以下操作之前调用 StartAsync
:
- 已配置应用的请求处理管道。
- 已启动服务器且已触发 IApplicationLifetime.ApplicationStarted。
可以更改默认行为,以便在配置应用的管道并调用 ApplicationStarted
之后,运行托管服务的 StartAsync
。 若要更改默认行为,请在调用 ConfigureWebHostDefaults
后添加托管服务(以下示例中的 VideosWatcher
):
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
public class Program
public static void Main(string[] args)
CreateHostBuilder(args).Build().Run();
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
webBuilder.UseStartup<Startup>();
.ConfigureServices(services =>
services.AddHostedService<VideosWatcher>();
StopAsync
- StopAsync(CancellationToken) 在主机执行正常关闭时触发。
StopAsync
包含结束后台任务的逻辑。 实现 IDisposable 和终结器(析构函数)以处置任何非托管资源。
默认情况下,取消令牌会有五秒超时,以指示关闭进程不再正常。 在令牌上请求取消时:
- 应中止应用正在执行的任何剩余后台操作。
StopAsync
中调用的任何方法都应及时返回。
但是,在请求取消后,将不会放弃任务,调用方会等待所有任务完成。
如果应用意外关闭(例如,应用的进程失败),则可能不会调用 StopAsync
。 因此,在 StopAsync
中执行的任何方法或操作都可能不会发生。
若要延长默认值为 5 秒的关闭超时值,请设置:
- ShutdownTimeout(当使用通用主机时)。 有关详细信息,请参阅 ASP.NET Core 中的 .NET 通用主机。
- 使用 Web 主机时为关闭超时值主机配置设置。 有关详细信息,请参阅 ASP.NET Core Web 主机。
托管服务在应用启动时激活一次,在应用关闭时正常关闭。 如果在执行后台任务期间引发错误,即使未调用 StopAsync
,也应调用 Dispose
。
BackgroundService 基类
BackgroundService 是用于实现长时间运行的 IHostedService 的基类。
调用 ExecuteAsync(CancellationToken) 来运行后台服务。 实现返回一个 Task,其表示后台服务的整个生存期。 在 ExecuteAsync 变为异步(例如通过调用 await
)之前,不会启动任何其他服务。 避免在 ExecuteAsync
中执行长时间的阻塞初始化工作。 StopAsync(CancellationToken) 中的主机块等待完成 ExecuteAsync
。
调用 IHostedService.StopAsync 时,将触发取消令牌。 当激发取消令牌以便正常关闭服务时,ExecuteAsync
的实现应立即完成。 否则,服务将在关闭超时后不正常关闭。 有关更多信息,请参阅 IHostedService interface 部分。
StartAsync
应仅限于短期任务,因为托管服务是按顺序运行的,在 StartAsync
运行完成之前不会启动其他服务。 长期任务应放置在 ExecuteAsync
中。 有关详细信息,请参阅 BackgroundService 数据源。
计时的后台任务
定时后台任务使用 System.Threading.Timer 类。 计时器触发任务的 DoWork
方法。 在 StopAsync
上禁用计时器,并在 Dispose
上处置服务容器时处置计时器:
public class TimedHostedService : IHostedService, IDisposable
private int executionCount = 0;
private readonly ILogger<TimedHostedService> _logger;
private Timer _timer;
public TimedHostedService(ILogger<TimedHostedService> logger)
_logger = logger;
public Task StartAsync(CancellationToken stoppingToken)
_logger.LogInformation("Timed Hosted Service running.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromSeconds(5));
return Task.CompletedTask;
private void DoWork(object state)
var count = Interlocked.Increment(ref executionCount);
_logger.LogInformation(
"Timed Hosted Service is working. Count: {Count}", count);
public Task StopAsync(CancellationToken stoppingToken)
_logger.LogInformation("Timed Hosted Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
public void Dispose()
_timer?.Dispose();
Timer 不等待先前的 DoWork
执行完成,因此所介绍的方法可能并不适用于所有场景。 使用 Interlocked.Increment 以原子操作的形式将执行计数器递增,这可确保多个线程不会并行更新 executionCount
。
已使用 AddHostedService
扩展方法在 IHostBuilder.ConfigureServices
(Program.cs
) 中注册该服务:
services.AddHostedService<TimedHostedService>();
在后台任务中使用有作用域的服务
要在 BackgroundService 中使用有作用域的服务,请创建作用域。 默认情况下,不会为托管服务创建作用域。
作用域后台任务服务包含后台任务的逻辑。 如下示例中:
- 服务是异步的。
DoWork
方法返回 Task
。 出于演示目的,在 DoWork
方法中等待 10 秒的延迟。
- ILogger 注入到服务中。
internal interface IScopedProcessingService
Task DoWork(CancellationToken stoppingToken);
internal class ScopedProcessingService : IScopedProcessingService
private int executionCount = 0;
private readonly ILogger _logger;
public ScopedProcessingService(ILogger<ScopedProcessingService> logger)
_logger = logger;
public async Task DoWork(CancellationToken stoppingToken)
while (!stoppingToken.IsCancellationRequested)
executionCount++;
_logger.LogInformation(
"Scoped Processing Service is working. Count: {Count}", executionCount);
await Task.Delay(10000, stoppingToken);
托管服务创建一个作用域来解决作用域后台任务服务以调用其 DoWork
方法。 DoWork
返回 ExecuteAsync
等待的 Task
:
public class ConsumeScopedServiceHostedService : BackgroundService
private readonly ILogger<ConsumeScopedServiceHostedService> _logger;
public ConsumeScopedServiceHostedService(IServiceProvider services,
ILogger<ConsumeScopedServiceHostedService> logger)
Services = services;
_logger = logger;
public IServiceProvider Services { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
_logger.LogInformation(
"Consume Scoped Service Hosted Service running.");
await DoWork(stoppingToken);
private async Task DoWork(CancellationToken stoppingToken)
_logger.LogInformation(
"Consume Scoped Service Hosted Service is working.");
using (var scope = Services.CreateScope())
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedProcessingService>();
await scopedProcessingService.DoWork(stoppingToken);
public override async Task StopAsync(CancellationToken stoppingToken)
_logger.LogInformation(
"Consume Scoped Service Hosted Service is stopping.");
await base.StopAsync(stoppingToken);
已在 IHostBuilder.ConfigureServices
(Program.cs
) 中注册这些服务。 已使用 AddHostedService
扩展方法注册托管服务:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
排队的后台任务
后台任务队列基于 .NET 4.x QueueBackgroundWorkItem:
public interface IBackgroundTaskQueue
ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem);
ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken);
public class BackgroundTaskQueue : IBackgroundTaskQueue
private readonly Channel<Func<CancellationToken, ValueTask>> _queue;
public BackgroundTaskQueue(int capacity)
// Capacity should be set based on the expected application load and
// number of concurrent threads accessing the queue.
// BoundedChannelFullMode.Wait will cause calls to WriteAsync() to return a task,
// which completes only when space became available. This leads to backpressure,
// in case too many publishers/calls start accumulating.
var options = new BoundedChannelOptions(capacity)
FullMode = BoundedChannelFullMode.Wait
_queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
public async ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem)
if (workItem == null)
throw new ArgumentNullException(nameof(workItem));
await _queue.Writer.WriteAsync(workItem);
public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken)
var workItem = await _queue.Reader.ReadAsync(cancellationToken);
return workItem;
在以下 QueueHostedService
示例中:
BackgroundProcessing
方法返回 ExecuteAsync
中等待的 Task
。
- 在
BackgroundProcessing
中,取消排队并执行队列中的后台任务。
- 服务在
StopAsync
中停止之前,将等待工作项。
public class QueuedHostedService : BackgroundService
private readonly ILogger<QueuedHostedService> _logger;
public QueuedHostedService(IBackgroundTaskQueue taskQueue,
ILogger<QueuedHostedService> logger)
TaskQueue = taskQueue;
_logger = logger;
public IBackgroundTaskQueue TaskQueue { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
_logger.LogInformation(
$"Queued Hosted Service is running.{Environment.NewLine}" +
$"{Environment.NewLine}Tap W to add a work item to the " +
$"background queue.{Environment.NewLine}");
await BackgroundProcessing(stoppingToken);
private async Task BackgroundProcessing(CancellationToken stoppingToken)
while (!stoppingToken.IsCancellationRequested)
var workItem =
await TaskQueue.DequeueAsync(stoppingToken);
await workItem(stoppingToken);
catch (Exception ex)
_logger.LogError(ex,
"Error occurred executing {WorkItem}.", nameof(workItem));
public override async Task StopAsync(CancellationToken stoppingToken)
_logger.LogInformation("Queued Hosted Service is stopping.");
await base.StopAsync(stoppingToken);
每当在输入设备上选择 w
键时,MonitorLoop
服务将处理托管服务的排队任务:
IBackgroundTaskQueue
注入到 MonitorLoop
服务中。
- 调用
IBackgroundTaskQueue.QueueBackgroundWorkItem
来将工作项排入队列。
- 工作项模拟长时间运行的后台任务:
- 将执行三次 5 秒的延迟 (
Task.Delay
)。
- 如果任务已取消,
try-catch
语句将捕获 OperationCanceledException。
private readonly IBackgroundTaskQueue _taskQueue;
private readonly ILogger _logger;
private readonly CancellationToken _cancellationToken;
public MonitorLoop(IBackgroundTaskQueue taskQueue,
ILogger<MonitorLoop> logger,
IHostApplicationLifetime applicationLifetime)
_taskQueue = taskQueue;
_logger = logger;
_cancellationToken = applicationLifetime.ApplicationStopping;
public void StartMonitorLoop()
_logger.LogInformation("MonitorAsync Loop is starting.");
// Run a console user input loop in a background thread
Task.Run(async () => await MonitorAsync());
private async ValueTask MonitorAsync()
while (!_cancellationToken.IsCancellationRequested)
var keyStroke = Console.ReadKey();
if (keyStroke.Key == ConsoleKey.W)
// Enqueue a background work item
await _taskQueue.QueueBackgroundWorkItemAsync(BuildWorkItem);
private async ValueTask BuildWorkItem(CancellationToken token)
// Simulate three 5-second tasks to complete
// for each enqueued work item
int delayLoop = 0;
var guid = Guid.NewGuid().ToString();
_logger.LogInformation("Queued Background Task {Guid} is starting.", guid);
while (!token.IsCancellationRequested && delayLoop < 3)
await Task.Delay(TimeSpan.FromSeconds(5), token);
catch (OperationCanceledException)
// Prevent throwing if the Delay is cancelled
delayLoop++;
_logger.LogInformation("Queued Background Task {Guid} is running. " + "{DelayLoop}/3", guid, delayLoop);
if (delayLoop == 3)
_logger.LogInformation("Queued Background Task {Guid} is complete.", guid);
_logger.LogInformation("Queued Background Task {Guid} was cancelled.", guid);
已在 IHostBuilder.ConfigureServices
(Program.cs
) 中注册这些服务。 已使用 AddHostedService
扩展方法注册托管服务:
services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue>(ctx => {
if (!int.TryParse(hostContext.Configuration["QueueCapacity"], out var queueCapacity))
queueCapacity = 100;
return new BackgroundTaskQueue(queueCapacity);
已在 Program.Main
中启动 MonitorLoop
:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
- 使用 IHostedService 和 BackgroundService 类在微服务中实现后台任务
- 在 Azure 应用服务中使用 WebJobs 运行后台任务
- Timer