Как запускать фоновые задачи в приложении NET Core
В этой записи блога я расскажу, как запускать фоновые задачи в веб-приложениях net Core, используя инфраструктуру и API, предоставляемые платформой ASP.Net Core. Существует множество сценариев, в которых вы хотите постоянно выполнять задачу в фоновом режиме. Часто мы создаем собственный интерфейс и классы и каким-то образом подключаем их к классу Startup для достижения этой функциональности. В этой записи блога я собираюсь рассказать о двух основных способах выполнения фоновых задач в веб-приложениях ASP.Net Core. Оба способа предоставляются базовой платформой ASP.Net «из коробки». Как я упоминал ранее, мы можем запускать фоновые задачи в ASP.NET Core, используя две разные конструкции, предоставляемые платформой ASP.NET Core.
Они заключаются в следующем:
- Во-первых, мы можем реализовать
IHostedServiceинтерфейс - Во-вторых, мы можем получить производный от
BackgroundServiceабстрактного базового класса
Фоновые задачи с использованием IHostedService
Теперь, когда проект готов, пришло время создать наши первые фоновые задачи.
Для создания постоянно выполняющейся фоновой задачи давайте представим, что у нас есть процесс печати. Он будет печатать возрастающее целое число.
Для этого я создам новый класс BackgroundPrinter. Этот класс будет реализовывать IHostedServiceинтерфейс.
Интерфейс IHostedServiceпредоставляет два метода: StartAsyncи StopAsync. Метод StartAsync— это место, с которого должна быть запущена задача. В то время как StopAsyncметод — это то, где мы должны реализовать логику, пока задача остановлена.
Несколько важных моментов, которые следует помнить о StartAsyncметоде:
- Во-первых,
StartAsyncметод вызывается платформой до вызоваConfigureметода класса.Startup - Во-вторых,
StartAsyncметод вызывается до запуска сервера.
Наконец, если реализация класса IHostedServiceиспользует какой-либо неуправляемый объект, класс должен реализовать IDisposableинтерфейс для удаления неуправляемых объектов.
Реализация IHostedService с объектом Timer
Для первой версии кода я реализую Timerвнутри BackgroundPrinterкласса. И через интервал таймера я распечатаю увеличенное число.
Во-первых, я объявлю в классе Timerобъект и целочисленную переменную .number
Во-вторых, внутри StartAsyncя создам новый экземпляр файла Timer.
В-третьих, для Timerделегата я определю анонимную функцию, в которой буду увеличивать и печатать целое число, используя ILoggerопределенную как переменную уровня класса.
В-четвертых, я настрою таймер на работу с 5-секундным интервалом.
Наконец, я реализую IDisposableинтерфейс и Disposeметод для вызова метода Timerобъекта Dispose.
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace BackgroundTask.Demo
{
public class BackgroundPrinter : IHostedService, IDisposable
{
private readonly ILogger<BackgroundPrinter> logger;
private Timer timer;
private int number;
public BackgroundPrinter(ILogger<BackgroundPrinter> logger,
IWorker worker)
{
this.logger = logger;
}
public void Dispose()
{
timer?.Dispose();
}
public Task StartAsync(CancellationToken cancellationToken)
{
timer = new Timer(o => {
Interlocked.Increment(ref number);
logger.LogInformation($"Printing the worker number {number}");
},
null,
TimeSpan.Zero,
TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
}Настройка класса BackgroundPrinter
Как только BackgroundPrinterкласс будет готов, мы настроим его для контейнера внедрения зависимостей, используя AddHostedServiceметод расширения в IServiceCollectionинтерфейсе.
Теперь мы можем сделать это либо в Startupклассе, либо в Programклассе. Я предпочитаю делать это в классе программы, просто для четкого разделения.
Поэтому я собираюсь обновить Programкласс, чтобы добиться этого. Для этого в CreateHostBuilderметоде я буду использовать ConfigureServicesметод расширения интерфейса .IHostBuilder
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace BackgroundTask.Demo
{
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<BackgroundPrinter>());
}
}Запуск приложения
Теперь я собираюсь запустить приложение, чтобы увидеть вывод в консоли.
Как только я запущу приложение, я смогу получить доступ к конечным точкам API. В то же время консоль будет печатать автоматически увеличивающееся число каждые 5 секунд в фоновом режиме.

Вывод фоновой задачи
Внедрение зависимостей с помощью фоновой задачи
В приложении промышленного масштаба мы, вероятно, не будем использовать логику внутри класса, реализующего IHostedService. Мы не будем делать это для того, чтобы обеспечить разделение задач и правильное определение ответственности.
В большинстве случаев логика того, что происходит при выполнении задачи, вероятно, будет инкапсулирована в отдельный класс. Следовательно, очень важно, чтобы внедрение зависимостей работало с фоновыми задачами.
Хорошая новость заключается в том, что, поскольку фоновые задачи настраиваются в файле IServiceCollection, любые типы, добавленные в контейнер внедрения зависимостей, будут доступны для фоновой задачи.
Рабочий класс
Чтобы продемонстрировать это, я создам класс Workerи интерфейс IWorker, который будет иметь логику увеличения и печати числа. И BackgroundPrinterкласс будет просто использовать интерфейс IWorkerдля выполнения логики.
Интерфейс IWorkerбудет иметь единственный метод DoWork. Примет DoWorkодин параметр — экземпляр CancellationTokenкласса.
Для рабочего класса внутри DoWorkметода вместо запуска таймера я создам цикл while. И whileцикл будет ждать запроса на отмену экземпляра CancellationToken. Экземпляр CancellationTokenбудет передан из StartAsyncметода BackgroundPrinterкласса.
Внутри whileцикла я увеличу целое число уровня класса и выведу его на консоль, используя метод ILogger. И в конце цикла whileя подожду Task.Delay5 секунд, прежде чем цикл выполнится снова.
using System.Threading;
using System.Threading.Tasks;
namespace BackgroundTask.Demo
{
public interface IWorker
{
Task DoWork(CancellationToken cancellationToken);
}
}
using Microsoft.Extensions.Logging;
using System.Threading;
using System.Threading.Tasks;
namespace BackgroundTask.Demo
{
public class Worker : IWorker
{
private readonly ILogger<Worker> logger;
private int number = 0;
public Worker(ILogger<Worker> logger)
{
this.logger = logger;
}
public async Task DoWork(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
Interlocked.Increment(ref number);
logger.LogInformation($"Worker printing number {number}");
await Task.Delay(1000 * 5);
}
}
}
}Как только Workerкласс будет готов, я обновлю его BackgroundPrinter, чтобы использовать IWorkerинтерфейс бизнес-логики.
Помимо инъекции ILogger, теперь я буду внедрять IWorkerеще и в конструктор класса BackgroundPrinter.
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Threading;
using System.Threading.Tasks;
namespace BackgroundTask.Demo
{
public class BackgroundPrinter : IHostedService
{
private readonly ILogger<BackgroundPrinter> logger;
private readonly IWorker worker;
public BackgroundPrinter(ILogger<BackgroundPrinter> logger,
IWorker worker)
{
this.logger = logger;
this.worker = worker;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
await worker.DoWork(cancellationToken);
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
}Наконец, я зарегистрирую Workerкласс в контейнере внедрения зависимостей внутри класса Startup. Я обновлю ConfigureServicesметод, чтобы добавить Workerкласс как одноэлементный экземпляр в контейнер внедрения зависимостей.
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace BackgroundTask.Demo
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSingleton<IWorker, Worker>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}Я не буду вносить никаких изменений в Programкласс, так как мы по-прежнему используем BackgroundPrinterкласс для фоновой задачи. Теперь, если я запущу приложение, я увижу в консоли тот же ответ, что и раньше.
Фоновые задачи с использованием BackgroundService
Использование BackgroundServiceабстрактного базового класса — это второй способ запуска фоновых задач. Реализация BackgroundServiceотносительно проще по сравнению с IHostedService. Но в то же время у вас меньше контроля над тем, как запускать и останавливать задачу.
Чтобы продемонстрировать, как BackgroundServiceработает абстрактный базовый класс, я создам новый класс DerivedBackgroundPrinter. Этот новый класс уедет из BackgroundServiceкласса.
И на этот раз я просто буду использовать IWorkerинтерфейс для бизнес-логики. Следовательно, я создам зависимость конструктора от IWorkerинтерфейса. Поскольку IWorkerинтерфейс уже настроен в контейнере внедрения зависимостей, он будет автоматически передан этому классу.
Абстрактный BackgroundServiceбазовый класс предоставляет единственный абстрактный метод ExecuteAsync. У нас будет реализация для вызова DoWorkинтерфейса IWorkerвнутри этого ExecuteAsyncметода.
using Microsoft.Extensions.Hosting;
using System.Threading;
using System.Threading.Tasks;
namespace BackgroundTask.Demo
{
public class DerivedBackgroundPrinter : BackgroundService
{
private readonly IWorker worker;
public DerivedBackgroundPrinter(IWorker worker)
{
this.worker = worker;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await worker.DoWork(stoppingToken);
}
}
}Как только этот класс будет готов, я обновлю его Program, чтобы использовать DerivedBackgroundPrinterего вместо BackgroundPrinterсредства запуска фоновых задач.
Следовательно, внутри CreateHostBuilderметода класса Programя заменю на BackgroundPrinterдля DerivedBackgroundPrinterвызова AddHostedServiceуниверсального метода расширения.
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace BackgroundTask.Demo
{
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<DerivedBackgroundPrinter>());
}
}Теперь, если я запущу приложение, я увижу тот же ответ, что и раньше.
Заключение
Как видите, создавать фоновые задачи с использованием IHostedServiceинтерфейса ASP.NET Core и BackgroundServiceабстрактного базового класса чрезвычайно просто. Самое приятное то, что нет никакой внешней зависимости от пакета NuGet.
Только полноправные пользователи могут оставлять комментарии. Аутентифицируйтесь пожалуйста, используя сервисы.