Net 5 中与 FileSystemWatcher 的异步使用

发布于 2025-01-12 18:47:27 字数 2089 浏览 1 评论 0原文

我正在尝试在 Net 5 中创建一个应用程序来监视文件夹,并且任何时候将文件放入文件夹中时,它都应该运行一组特定的任务(从文件中获取一些信息,将它们复制到新位置等) )。 我以为我可以实现 Net 的 FileSystemWatcher 和并发集合(来自此处的 Collections.Concurrent 命名空间,但我遇到了无法异步运行它的问题。

我像这样初始化观察器(遵循 MS 的文档):

public BlockingCollection<string> fileNames = new BlockingCollection<string>();
public void InitiateWatcher()
{
    using FileSystemWatcher watcher = new FileSystemWatcher(@"C:\temp"); //test dir

    watcher.NotifyFilter = NotifyFilters.Attributes
             | NotifyFilters.CreationTime
             | NotifyFilters.DirectoryName
             | NotifyFilters.FileName

    watcher.Created += OnCreated;

    watcher.Filter = "*.*";
    watcher.IncludeSubdirectories = true;
    watcher.EnableRaisingEvents = true;

    Console.WriteLine("Press e to exit.");
    Console.ReadLine();
}

private void OnCreated(object sender, FileSystemEventArgs e)
{
    string value = $"Created: {e.FullPath}";
    // this of course creates problems, since it is a async task running in sync mode.
    await PipelineStages.ReadFilenamesAsync(_sourcePath, fileNames);
    // do other work with the result from ReadFilenameAsync
    Console.WriteLine(value);
}

我的 PipelineStages 类,它对文件进行大部分实际工作,如下所示:

public static class PipelineStages
{
    public static Task ReadFilenamesAsync(string path, BlockingCollection<string> output)
    {
        return Task.Factory.StartNew(() =>
        {
            foreach (string fileName in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories))
            {
                output.Add(fileName);
            }
            output.CompleteAdding();
        }, TaskCreationOptions.LongRunning);
    }
}

如果我将 OnCreated 方法设为异步,它会抛出返回类型的错误这对我来说有点道理,尽管我不知道前进的方向,但

我看到两个错误: 一个错误是,当代码到达 output.add(fileName) 行时,它会抛出 System.InvalidOperationException:“集合已被标记为关于添加的完成。”

另一个是,我注意到在 onCreated 方法中,文件名被写入控制台,这些文件位于非常不同的文件夹中,看起来是随机的。

所以,基本上,我有两个问题:

  • 这是正确的方法吗? (如果没有,还有什么其他选择)
  • 如果这是正确的方法,我该如何解决这些问题?

I'm trying to create an application in Net 5 that watches a folder and any time files are being dropped in the folder, it should run a certain set of tasks (getting some info from the files, copy them to new location, among others).
I thought I could implement both Net's FileSystemWatcher and concurrent collections (from the Collections.Concurrent namespace here, but I run into the problem that I cannot run it with being async.

I initialize the watcher like this (following the docs of MS):

public BlockingCollection<string> fileNames = new BlockingCollection<string>();
public void InitiateWatcher()
{
    using FileSystemWatcher watcher = new FileSystemWatcher(@"C:\temp"); //test dir

    watcher.NotifyFilter = NotifyFilters.Attributes
             | NotifyFilters.CreationTime
             | NotifyFilters.DirectoryName
             | NotifyFilters.FileName

    watcher.Created += OnCreated;

    watcher.Filter = "*.*";
    watcher.IncludeSubdirectories = true;
    watcher.EnableRaisingEvents = true;

    Console.WriteLine("Press e to exit.");
    Console.ReadLine();
}

private void OnCreated(object sender, FileSystemEventArgs e)
{
    string value = 
quot;Created: {e.FullPath}";
    // this of course creates problems, since it is a async task running in sync mode.
    await PipelineStages.ReadFilenamesAsync(_sourcePath, fileNames);
    // do other work with the result from ReadFilenameAsync
    Console.WriteLine(value);
}

My PipelineStages class, which does most of the real work with the files, looks like this:

public static class PipelineStages
{
    public static Task ReadFilenamesAsync(string path, BlockingCollection<string> output)
    {
        return Task.Factory.StartNew(() =>
        {
            foreach (string fileName in Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories))
            {
                output.Add(fileName);
            }
            output.CompleteAdding();
        }, TaskCreationOptions.LongRunning);
    }
}

If I turn the OnCreated method async it throws error that the return type is not valid. That kinda makes sense to me, although I don't know the way forward on this.

I see two errors:
One error is when the code hits the output.add(fileName) line, it throws an System.InvalidOperationException: 'The collection has been marked as complete with regards to additions.'

The other one is that I notice in the onCreated method that filenames get written to the console that are in very different folders, seemingly randomly.

So, basically, I have two questions:

  • Is this the right approach? (If not, what are other alternatives)
  • If this is the right approach, how can I fix these problems?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

深府石板幽径 2025-01-19 18:47:27

首先,您似乎过早地调用了 output.CompleteAdding(),因此每次执行 PipelineStages.ReadFilenamesAsync 都会收到上述 System.InvalidOperationException 错误。
其次,虽然 async void 通常确实不鼓励,但在以下情况下是允许的:

  • 事件处理程序
  • Main 方法

,但只要这样的 async void code> 事件处理程序基本上是一个即发即忘函数,您应该使其对于任何捕获的变量(其状态可以在外部作用域中无效)(已处置,CompleteAdding 等) .
我编写了以下异步设计的简短独立示例来演示我想要表达的观点:

using System.Collections.Concurrent;
using System.Diagnostics;

object lockObj = new();
using var watcher = new FileSystemWatcher(@".");
using BlockingCollection<string?> queue = new();

async void FileCreatedHandlerAsync(object _, FileSystemEventArgs e) => await Task.Run(() => {
  var lockTaken = false;
  try
  {
    Monitor.Enter(lockObj, ref lockTaken);
    if (!queue.IsCompleted)
      queue.Add(e.Name);
  }
  catch (ObjectDisposedException) { }
  finally {
    if (lockTaken)
      Monitor.Exit(lockObj);
  }
});
watcher.Created += FileCreatedHandlerAsync;
watcher.EnableRaisingEvents = true;

var consumer = Task.Run(async () => {
  foreach (string? name in queue.GetConsumingEnumerable())
    await Task.Run(() => Console.WriteLine($"File has been created: \"{name}\"."));
  
  Debug.Assert(queue.IsCompleted);
});

Console.WriteLine("Press any key to exit.");
Console.ReadLine();

watcher.Created -= FileCreatedHandlerAsync;
lock (lockObj) {
  queue.CompleteAdding();
}
bool completed = consumer.Wait(100);
Debug.Assert(completed && TaskStatus.RanToCompletion == consumer.Status); 

Firstly, it looks like you prematurely call output.CompleteAdding(), so every subsequent execution of PipelineStages.ReadFilenamesAsync gets the foregoing System.InvalidOperationException error.
Secondly, while async void is indeed generally discouraged, it is admissible in the following cases:

  • event handlers
  • the Main method

BUT as long as such an async void event handler is basically a fire‑and‑forget function, you should make it bulletproof with regard to any captured variables whose state can be invalidated in the outer scope (disposed, CompleteAdding, and the like).
I have rigged up the following async‑contrived short self‑contained example to demonstrate the point I am trying to make:

using System.Collections.Concurrent;
using System.Diagnostics;

object lockObj = new();
using var watcher = new FileSystemWatcher(@".");
using BlockingCollection<string?> queue = new();

async void FileCreatedHandlerAsync(object _, FileSystemEventArgs e) => await Task.Run(() => {
  var lockTaken = false;
  try
  {
    Monitor.Enter(lockObj, ref lockTaken);
    if (!queue.IsCompleted)
      queue.Add(e.Name);
  }
  catch (ObjectDisposedException) { }
  finally {
    if (lockTaken)
      Monitor.Exit(lockObj);
  }
});
watcher.Created += FileCreatedHandlerAsync;
watcher.EnableRaisingEvents = true;

var consumer = Task.Run(async () => {
  foreach (string? name in queue.GetConsumingEnumerable())
    await Task.Run(() => Console.WriteLine(
quot;File has been created: \"{name}\"."));
  
  Debug.Assert(queue.IsCompleted);
});

Console.WriteLine("Press any key to exit.");
Console.ReadLine();

watcher.Created -= FileCreatedHandlerAsync;
lock (lockObj) {
  queue.CompleteAdding();
}
bool completed = consumer.Wait(100);
Debug.Assert(completed && TaskStatus.RanToCompletion == consumer.Status); 
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文