目录
介绍
.NET中的辅助角色服务模板是用于长时间运行和资源密集型任务的项目模板,最好将其调节为在后台运行无外设(无UI)。.NET的后台服务实现允许用户访问跨平台目标,并且由于它使用通用主机机制,它使我们能够访问常用的东西,例如我们可能已经习惯的依赖项注入链、日志记录和配置。.NET 文件系统观察器是一个组件,用于监视来自系统的更改通知的目录,例如正在添加、更新或删除的文件。
计划
- 我们还将使用.NET CLI创建一个辅助角色服务项目。
- 我们将创建一个服务类,该类利用FileSystemWatcher来监视指定目录的更改。
- 当发生文件添加时,我们将使用该文件并从中读取行。为此,我们将拥有一个作用域服务类,该类将充当我们的文件使用者。我们将使用DI链的服务提供商在有限的范围内实现这个类。
你需要什么
- .NET SDK,我使用的是6.0
- IDE我正在使用VS 2022社区版
设置
使用.NET CLI工具初始化项目。我在Windows上,我使用了PowerShell:
dotnet new worker --name FileWatching
FileConsumerService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FileWatching
{
public class FileConsumerService : IFileConsumerService
{
ILogger<fileconsumerservice> _logger;
public FileConsumerService(ILogger<fileconsumerservice> logger)
{
_logger = logger;
}
public async Task ConsumeFile(string pathToFile)
{
if(!File.Exists(pathToFile))
return;
_logger.LogInformation($"Starting read of {pathToFile}");
using (StreamReader sr = File.OpenText(pathToFile))
{
string? s = null;
int counter = 1;
while ((s = await sr.ReadLineAsync()) != null)
{
_logger.LogInformation($"Reading Line {counter}
of the file {pathToFile}");
counter++;
}
}
_logger.LogInformation($"Completed read of {pathToFile}");
}
}
}
我们将从第一个类开始,即我们的使用者类——此服务的职责是简单地获取新添加文件的路径,然后逐行使用它。在此示例中,我使用了一些CSV文件。这是您通常读取数据并可能对其进行其他操作(例如将其插入数据库)的地方。请注意,主ConsumeFile函数是异步完成的。这使我们能够在另一个线程上执行这个运行时间更长的过程,并能够快速返回到使用此服务的类。如您所见,这将处理连续丢弃大量文件的问题。
MyFileWatcher.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FileWatching
{
public class MyFileWatcher : IMyFileWatcher
{
private string _directoryName = Path.Join
(Environment.CurrentDirectory, "files");//change this to whatever you want
private string _fileFilter = "*.*";
FileSystemWatcher _fileSystemWatcher;
ILogger<myfilewatcher> _logger;
IServiceProvider _serviceProvider;
public MyFileWatcher(ILogger<myfilewatcher> logger,
IServiceProvider serviceProvider)
{
_logger = logger;
if(!Directory.Exists(_directoryName))
Directory.CreateDirectory(_directoryName);
_fileSystemWatcher = new FileSystemWatcher(_directoryName, _fileFilter);
_serviceProvider = serviceProvider;
}
public void Start()
{
_fileSystemWatcher.NotifyFilter = NotifyFilters.Attributes
| NotifyFilters.CreationTime
| NotifyFilters.DirectoryName
| NotifyFilters.FileName
| NotifyFilters.LastAccess
| NotifyFilters.LastWrite
| NotifyFilters.Security
| NotifyFilters.Size;
_fileSystemWatcher.Changed += _fileSystemWatcher_Changed;
_fileSystemWatcher.Created += _fileSystemWatcher_Created;
_fileSystemWatcher.Deleted += _fileSystemWatcher_Deleted;
_fileSystemWatcher.Renamed += _fileSystemWatcher_Renamed;
_fileSystemWatcher.Error += _fileSystemWatcher_Error;
_fileSystemWatcher.EnableRaisingEvents = true;
_fileSystemWatcher.IncludeSubdirectories = true;
_logger.LogInformation($"File Watching has started
for directory {_directoryName}");
}
private void _fileSystemWatcher_Error(object sender, ErrorEventArgs e)
{
_logger.LogInformation($"File error event {e.GetException().Message}");
}
private void _fileSystemWatcher_Renamed(object sender, RenamedEventArgs e)
{
_logger.LogInformation($"File rename event for file {e.FullPath}");
}
private void _fileSystemWatcher_Deleted(object sender, FileSystemEventArgs e)
{
_logger.LogInformation($"File deleted event for file {e.FullPath}");
}
private void _fileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
{
}
private void _fileSystemWatcher_Created(object sender, FileSystemEventArgs e)
{
using (var scope = _serviceProvider.CreateScope())
{
var consumerService = scope.ServiceProvider.GetRequiredService
<ifileconsumerservice>();
Task.Run(() => consumerService.ConsumeFile(e.FullPath));
}
}
}
}
这是应用程序的主力。该类实现FileSystemWatcher组件并描述:
- 我们要观看的目录
- 我们要注意什么类型的文件
- 我们对这些文件的哪些属性感兴趣
- 我们要处理哪些通知事件
- 当这些事件发生时,我们想做什么
private string _directoryName =
Path.Join(Environment.CurrentDirectory, "files");//change this to whatever you want
简单。此属性是我们想要监视的目录。您可以将其更改为您希望FileSystemWatcher组件观察的任何目录。
private string _fileFilter = "*.*";
另一个简单的。这一行几乎描述了我们想要的文件的过滤器,如果我们只想要文本文件,我们会使用“*.txt”。根据需要进行调整。
public MyFileWatcher(ILogger<myfilewatcher> logger, IServiceProvider serviceProvider)
{
_logger = logger;
if(!Directory.Exists(_directoryName))
Directory.CreateDirectory(_directoryName);
_fileSystemWatcher = new FileSystemWatcher(_directoryName, _fileFilter);
_serviceProvider = serviceProvider;
}
这是此类的构造函数。我们正在使用GenericHost的DI机制来注入记录器和DI的serviceProvider。我们要做的一件事是检查目录是否存在,如果不存在,我们创建它。我们使用构造函数初始化FileSystemWatcher,该构造函数获取我们要监视的目录和要监视的文件的过滤器。此类将用作我们应用程序的单例。这意味着在应用程序的生命周期中只存在一个实例。因此,我们执行一次目录检查,但如您所见,我们将使用serviceProvider为创建的每个新文件创建使用者类的作用域实例。
_fileSystemWatcher.NotifyFilter = NotifyFilters.Attributes
| NotifyFilters.CreationTime
| NotifyFilters.DirectoryName
| NotifyFilters.FileName
| NotifyFilters.LastAccess
| NotifyFilters.LastWrite
| NotifyFilters.Security
| NotifyFilters.Size;
在我们的start方法中,我们要做的第一件事是设置通知过滤器。这些筛选器是我们希望收到通知的文件的属性。我们告诉FileSystemWatcher当这些属性中的任何一个发生更改时,我们希望触发通知事件。将此集修改为有兴趣观察更改的属性。
_fileSystemWatcher.Changed += _fileSystemWatcher_Changed;
_fileSystemWatcher.Created += _fileSystemWatcher_Created;
_fileSystemWatcher.Deleted += _fileSystemWatcher_Deleted;
_fileSystemWatcher.Renamed += _fileSystemWatcher_Renamed;
_fileSystemWatcher.Error += _fileSystemWatcher_Error;
这些是我们想要观看的文件的事件。当这些操作之一发生在我们的文件上时,我们希望声明我们必须发生的事情。
_fileSystemWatcher.EnableRaisingEvents = true;
_fileSystemWatcher.IncludeSubdirectories = true;
_logger.LogInformation($"File Watching has started for directory
{_directoryName}");
最后,该EnableRaisingEvents属性设置为“true”。这只是FileSystemWatcher的“on”开关。如果我们希望观察程序停止,但程序继续,我们将此属性设置为false。IncludeSubDirectories非常简单,如果是true,它将监视您声明目录中的子目录,如果是false,它将只执行基本目录。我们还记录了我们的观察程序类是否成功启动。
private void _fileSystemWatcher_Created(object sender, FileSystemEventArgs e)
{
using (var scope = _serviceProvider.CreateScope())
{
var consumerService =
scope.ServiceProvider.GetRequiredService<ifileconsumerservice>();
Task.Run(() => consumerService.ConsumeFile(e.FullPath));
}
}
在此类中要注意的最后一件事是创建文件时的事件。创建文件时,我们使用serviceProvider来创建服务范围。然后,我们检索使用者服务的作用域实例,然后将任务触发到ConsumeFile。我们使用FileSystemEventArgs对象传递文件的路径。消费者服务的生存期仅在此范围内。这使我们能够使用GenericHost内置的DI机制以高效和正确的方式处理我们consumerService的分配。
Worker.cs
namespace FileWatching;
public class Worker : BackgroundService
{
private readonly ILogger<worker> _logger;
private readonly IMyFileWatcher _watcher;
public Worker(ILogger<worker> logger,IMyFileWatcher watcher)
{
_logger = logger;
_watcher = watcher;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_watcher.Start();
while (!stoppingToken.IsCancellationRequested)
{
}
}
}
worker类是实际在后台运行的类。它实现了具有我们需要让程序启动并在后台运行直到获得停止信号所需的必要东西的BackgroundService类。首先,我们注入记录器和我们FileWatcher服务的实例。我们重写基类的ExecuteAsync方法,该方法在主机声明了辅助角色服务时触发。我们只是调用FileWatcher服务的“Start”方法。接下来,我们有一个循环,它只是流动,直到我们收到取消请求,例如,在调试期间的控制台上,通过按 Ctrl+C 命令完成,或者当它部署为Windows服务时,通过告诉服务停止来完成。
Program.cs
using FileWatching;
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddHostedService<worker>();
services.AddSingleton<imyfilewatcher,myfilewatcher>();
services.AddScoped<ifileconsumerservice,fileconsumerservice>();
})
.Build();
await host.RunAsync();
此类设置所有内容。我们使用我们的通用主机的ConfigureServices函数通过使用AddHostedService函数来设置Worker类的注入。接下来,我们将文件观察程序服务添加为使用该AddSingleton函数的单例。然后,我们告诉服务注入器,当我们需要FileConsumer服务的实例时,我们会将其作为作用域实例来执行。
当我们运行它时,应用程序将在运行目录中创建一个“files”目录。
现在,如果你把一些文件放到“files”目录中,你应该看到事件触发的结果,我们看到“created”事件触发,我们看到消费者类的函数记录文件中的读出行。我包含了一些用于此的示例文件。
其他有用信息
https://www.codeproject.com/Articles/5344573/Using-NET-Background-Worker-Service-With-FileSyste