Сервис должен представлять реализацию интерфейса IHostedService, который определяет два метода:
StartAsync() – вызывается при запуске сервиса, обычно содержит код запуска выполняемой в рамках сервиса фоновой задачи
StopAsync() – вызывается, когда приложение завершает работу, и обычно содержит код завершения фоновой задачи и освобождения используемых ресурсов.
Следует учитывать, что если приложение завершается аварийно, то метод StopAsync и соответственно его логика может не вызываться.
То есть если вкратце, мы создем сервис для выполнения какой-то задачи. В методе StartAsync запускаем эту задачу, а в StopAsync контроллируем ее завершение.
Также .NET предоставляет готовую реализацию этого интерфейса в виде абстрактного класса BackgroundService
public abstract class BackgroundService : IHostedService, IDisposable { public virtual void Dispose(); public virtual Task StartAsync(CancellationToken cancellationToken); public virtual Task StopAsync(CancellationToken cancellationToken); protected abstract Task ExecuteAsync(CancellationToken stoppingToken); }
Он добавляет еще один метод - ExecuteAsync(), который запускает фоновый сервис:
protected abstract Task ExecuteAsync(CancellationToken stoppingToken);
Этот метод запускается в методе StartAsync()
. Таким образом, мы можем унаследовать класс сервиса от BackgroundService и всю логику сервиса определить в методе
ExecuteAsync()
, собственно как это и делается в сервисе Worker по умолчанию.
Реализуем простейший сервис, который отслеживает изменения в определенной папке. Для этого определим следующую программу:
IHost host = Host.CreateDefaultBuilder(args) .ConfigureServices(services => { services.AddHostedService<FileWatcherService>(); }) .Build(); host.Run(); public class FileWatcherService : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { var filePath = "folderEvents.txt"; using var watcher = new FileSystemWatcher("D:\\Temp"); // записываем изменения watcher.Changed += async (o, e) => await File.AppendAllTextAsync(filePath, $"{DateTime.Now} Changed: {e.FullPath}\n"); // записываем данные о создании файлов и папок watcher.Created += async (o, e) => await File.AppendAllTextAsync(filePath, $"{DateTime.Now} Created: {e.FullPath}\n"); // записываем данные об удалении файлов и папок watcher.Deleted += async (o, e) => await File.AppendAllTextAsync(filePath, $"{DateTime.Now} Deleted: {e.FullPath}\n"); // записываем данные о переименовании watcher.Renamed += async (o, e) => await File.AppendAllTextAsync(filePath, $"{DateTime.Now} Renamed: {e.OldFullPath} to {e.FullPath}\n"); // записываем данные об ошибках watcher.Error += async (o, e) => await File.AppendAllTextAsync(filePath, $"{DateTime.Now} Error: {e.GetException().Message}\n"); watcher.IncludeSubdirectories = true; // отслеживаем изменения в подкаталогах watcher.EnableRaisingEvents = true; // включаем события // если операция не отменена, то выполняем задержку в 200 миллисекунд while (!stoppingToken.IsCancellationRequested) { await Task.Delay(200, stoppingToken); } await Task.CompletedTask; } }
Итак, здесь определен сервис FileWatcherService, который унаследован от BackgroundService. В методе ExecuteAsync создаем объект FileSystemWatcher, который будет отслеживать события в папке "D:\\Temp"
using var watcher = new FileSystemWatcher("D:\\Temp");
Далее прикрепляем обработчики к каждому из событий файловой системы, в которых записываем в файл соответствующую информацию:
// записываем изменения watcher.Changed += async (o, e) => await File.AppendAllTextAsync(filePath, $"{DateTime.Now} Changed: {e.FullPath}\n"); // записываем данные о создании файлов и папок watcher.Created += async (o, e) => await File.AppendAllTextAsync(filePath, $"{DateTime.Now} Created: {e.FullPath}\n"); // записываем данные об удалении файлов и папок watcher.Deleted += async (o, e) => await File.AppendAllTextAsync(filePath, $"{DateTime.Now} Deleted: {e.FullPath}\n"); // записываем данные о переименовании watcher.Renamed += async (o, e) => await File.AppendAllTextAsync(filePath, $"{DateTime.Now} Renamed: {e.OldFullPath} to {e.FullPath}\n"); // записываем данные об ошибках watcher.Error += async (o, e) => await File.AppendAllTextAsync(filePath, $"{DateTime.Now} Error: {e.GetException().Message}\n");
Затем запускается цикл, который выполняется пока для переданного токена не поступит запрос отмены операции
while (!stoppingToken.IsCancellationRequested) { await Task.Delay(200, stoppingToken); }
Собственно пока выполняется цикл, будет идти отслеживание событий в папке.
И добавляем сервис FileWatcherService в коллекцию сервисов хоста:
IHost host = Host.CreateDefaultBuilder(args) .ConfigureServices(services => { services.AddHostedService<FileWatcherService>(); }) .Build();
Запустим приложения и произведем в папке D:\\Temp различные действия с файлами - создание, удаление, переименование. В итоге в проекте появится текстовый файл "folderEvents.txt", который будет содержать строки типа:
16.11.2022 20:24:30 Created: D:\Temp\Текстовый документ.txt 16.11.2022 20:24:35 Renamed: D:\Temp\Текстовый документ.txt to D:\Temp\hello.txt 16.11.2022 20:24:39 Created: D:\Temp\Текстовый документ.txt 16.11.2022 20:24:42 Renamed: D:\Temp\Текстовый документ.txt to D:\Temp\test.txt 16.11.2022 20:24:46 Deleted: D:\Temp\hello.txt
Пример аналогичного сервиса в виде реализации интерфейса IHostedService:
IHost host = Host.CreateDefaultBuilder(args) .ConfigureServices(services => { services.AddHostedService<FileWatcherService2>(); }) .Build(); host.Run(); public class FileWatcherService2 : IHostedService { FileSystemWatcher? watcher; string filePath = "folderEvents.txt"; public async Task StartAsync(CancellationToken cancellationToken) { watcher = new FileSystemWatcher("D:\\Temp"); // записываем изменения watcher.Changed += async (o, e) => await File.AppendAllTextAsync(filePath, $"{DateTime.Now} Changed: {e.FullPath}\n"); // записываем данные о создании файлов и папок watcher.Created += async (o, e) => await File.AppendAllTextAsync(filePath, $"{DateTime.Now} Created: {e.FullPath}\n"); // записываем данные об удалении файлов и папок watcher.Deleted += async (o, e) => await File.AppendAllTextAsync(filePath, $"{DateTime.Now} Deleted: {e.FullPath}\n"); // записываем данные о переименовании watcher.Renamed += async (o, e) => await File.AppendAllTextAsync(filePath, $"{DateTime.Now} Renamed: {e.OldFullPath} to {e.FullPath}\n"); // записываем данные об ошибках watcher.Error += async (o, e) => await File.AppendAllTextAsync(filePath, $"{DateTime.Now} Error: {e.GetException().Message}\n"); watcher.IncludeSubdirectories = true; // отслеживаем изменения в подкаталогах watcher.EnableRaisingEvents = true; // включаем события await Task.CompletedTask; } public async Task StopAsync(CancellationToken cancellationToken) { watcher?.Dispose(); await Task.CompletedTask; } }