使用FileSystemWatcher和C#处理具有多个文件的文件夹

时间:2018-01-11 21:03:39

标签: c# filesystemwatcher

我创建了一个相对简单的Windows应用程序,可以监视文件夹。当在文件夹中创建新文件时,应用程序(通过FileSystemWatcher)将打开文件并处理内容。简而言之,内容与Selenium一起用于通过IE11自动化网页。此处理每个文件大约需要20秒。

问题是,如果在大致相同的时间或应用程序正在处理文件时在文件夹中创建了多个文件,则FileSystemWatcher onCreated不会看到下一个文件。因此,当处理在第一个文件上完成时,应用程序就会停止。同时,文件夹中有一个文件无法处理。如果在onCreated处理完成后添加了一个文件,它可以正常工作并处理下一个文件。

有人可以指导我解决这个问题吗?过分的细节非常受欢迎。

3 个答案:

答案 0 :(得分:4)

FileSystemWatcher(正如您已经注意到的)不可靠,您将始终为丢失的文件添加"自定义" /手动逻辑(另请注意,您可能会看到同一文件的多个事件

下面你可以看到一个带有"背景"的简单例子。检查未处理的文件 您可以通过使用并发集合来避免锁定,例如BlockingCollection
您也可以选择并行处理文件 我根据计时器处理文件,但您可以使用自己的策略 如果您不想实时处理文件,可能您甚至不需要FileSystemWatcher

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;

namespace ConsoleAppDemo
{
    class Program
    {
        private static object lockIbj = new object();
        private static List<string> _proccessedFiles = new List<string>();
        private static readonly List<string> toProccessFiles = new List<string>();
        private static List<string> _proccessingFiles = new List<string>();
        private const string directory = @"C:\Path";
        private const string extension = @"*.txt";
        static void Main(string[] args)
        {
            FileSystemWatcher f = new FileSystemWatcher();
            f.Path = directory;
            f.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
                             | NotifyFilters.FileName | NotifyFilters.DirectoryName;
            f.Filter = extension ;
            f.Created += F_Created;
            f.EnableRaisingEvents = true;

            Timer manualWatcher = new Timer(ManuallWatcherCallback, null, 0, 3000);

            Timer manualTaskRunner = new Timer(ManuallRunnerCallback, null, 0, 10000);

            Console.ReadLine();
        }

        private static void F_Created(object sender, FileSystemEventArgs e)
        {
            lock (lockIbj)
            {
                toProccessFiles.Add(e.FullPath);
                Console.WriteLine("Adding new File from watcher: " + e.FullPath);
            }

        }

        private static void ManuallWatcherCallback(Object o)
        {
            var files = Directory.GetFiles(directory, extension);
            lock (lockIbj)
            {
                foreach (var file in files)
                {
                    if (!_proccessedFiles.Contains(file) && !toProccessFiles.Contains(file) && !_proccessingFiles.Contains(file))
                    {
                        toProccessFiles.Add(file);
                        Console.WriteLine("Adding new File from manuall timer: " + file);
                    }
                }

            }
        }

        private static bool processing;
        private static void ManuallRunnerCallback(Object o)
        {
            if (processing)
                return;

            while (true)
            {
                //you could proccess file in parallel
                string fileToProcces = null;

                lock (lockIbj)
                {
                    fileToProcces = toProccessFiles.FirstOrDefault();
                    if (fileToProcces != null)
                    {
                        processing = true;
                        toProccessFiles.Remove(fileToProcces);
                        _proccessingFiles.Add(fileToProcces);
                    }
                    else
                    {
                        processing = false;
                        break;


                    }
                }

                if (fileToProcces == null)
                    return;

                //Must add error handling
                ProccessFile(fileToProcces);
            }
        }

        private static void ProccessFile(string fileToProcces)
        {
            Console.WriteLine("Processing:" + fileToProcces);
            lock (lockIbj)
            {
                _proccessingFiles.Remove(fileToProcces);
                _proccessedFiles.Add(fileToProcces);
            }
        }
    }
}

答案 1 :(得分:0)

您可以使用P / Invoke运行Win32文件系统更改通知功能,而不是使用FileSystemWatcher,并在文件系统发生更改时循环:

[DllImport("kernel32.dll", EntryPoint = "FindFirstChangeNotification")]
static extern System.IntPtr FindFirstChangeNotification (string lpPathName, bool bWatchSubtree, uint dwNotifyFilter);

[DllImport("kernel32.dll", EntryPoint = "FindNextChangeNotification")]
static extern bool FindNextChangeNotification (System.IntPtr hChangedHandle);

[DllImport("kernel32.dll", EntryPoint = "FindCloseChangeNotification")]
static extern bool FindCloseChangeNotification (System.IntPtr hChangedHandle);

[DllImport("kernel32.dll", EntryPoint = "WaitForSingleObject")]
static extern uint WaitForSingleObject (System.IntPtr handle, uint dwMilliseconds);

[DllImport("kernel32.dll", EntryPoint = "ReadDirectoryChangesW")]
static extern bool ReadDirectoryChangesW(System.IntPtr hDirectory, System.IntPtr lpBuffer, uint nBufferLength, bool bWatchSubtree, uint dwNotifyFilter, out uint lpBytesReturned, System.IntPtr lpOverlapped, ReadDirectoryChangesDelegate lpCompletionRoutine);

基本上,您使用要监视的目录调用FindFirstChangeNotification,这会为您提供等待句柄。然后使用句柄调用WaitForSingleObject,当它返回时,您知道发生了一个或多个更改。然后,您调用ReadDirectoryChangesW以找出已更改的内容,并处理更改。调用FindNextChangeNotification会为您提供等待下一次更改文件系统的句柄,因此您可能会调用此方法,然后调用WaitForSingleObject,然后在循环中调用ReadDirectoryChangesW。完成后,请致电FindCloseChangeNotification以停止跟踪更改。

编辑:这是一个更完整的例子:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;

[DllImport("kernel32.dll", EntryPoint = "FindFirstChangeNotification")]
static extern System.IntPtr FindFirstChangeNotification(string lpPathName, bool bWatchSubtree, uint dwNotifyFilter);

[DllImport("kernel32.dll", EntryPoint = "FindNextChangeNotification")]
static extern bool FindNextChangeNotification(System.IntPtr hChangedHandle);

[DllImport("kernel32.dll", EntryPoint = "FindCloseChangeNotification")]
static extern bool FindCloseChangeNotification(System.IntPtr hChangedHandle);

[DllImport("kernel32.dll", EntryPoint = "WaitForSingleObject")]
static extern uint WaitForSingleObject(System.IntPtr handle, uint dwMilliseconds);

[DllImport("kernel32.dll", EntryPoint = "ReadDirectoryChangesW")]
static extern bool ReadDirectoryChangesW(System.IntPtr hDirectory, System.IntPtr lpBuffer, uint nBufferLength, bool bWatchSubtree, uint dwNotifyFilter, out uint lpBytesReturned, System.IntPtr lpOverlapped, IntPtr lpCompletionRoutine);

[DllImport("kernel32.dll", EntryPoint = "CreateFile")]
public static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr SecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);

enum FileSystemNotifications
{
    FileNameChanged = 0x00000001,
    DirectoryNameChanged = 0x00000002,
    FileAttributesChanged = 0x00000004,
    FileSizeChanged = 0x00000008,
    FileModified = 0x00000010,
    FileSecurityChanged = 0x00000100,
}

enum FileActions
{
    FileAdded = 0x00000001,
    FileRemoved = 0x00000002,
    FileModified = 0x00000003,
    FileRenamedOld = 0x00000004,
    FileRenamedNew = 0x00000005
}

enum FileEventType
{
    FileAdded,
    FileChanged,
    FileDeleted,
    FileRenamed
}

class FileEvent
{
    private readonly FileEventType eventType;
    private readonly FileInfo file;

    public FileEvent(string fileName, FileEventType eventType)
    {
        this.file = new FileInfo(fileName);
        this.eventType = eventType;
    }

    public FileEventType EventType => eventType;
    public FileInfo File => file;
}

[StructLayout(LayoutKind.Sequential)]
struct FileNotifyInformation
{
    public int NextEntryOffset;
    public int Action;
    public int FileNameLength;
    public IntPtr FileName;
}

class DirectoryWatcher
{
    private const int MaxChanges = 4096;
    private readonly DirectoryInfo directory;

    public DirectoryWatcher(string dirPath)
    {
        this.directory = new DirectoryInfo(dirPath);
    }

    public IEnumerable<FileEvent> Watch(bool watchSubFolders = false)
    {
        var directoryHandle = CreateFile(directory.FullName, 0x80000000, 0x00000007, IntPtr.Zero, 3, 0x02000000, IntPtr.Zero);    
        var fileCreatedDeletedOrUpdated = FileSystemNotifications.FileNameChanged | FileSystemNotifications.FileModified;
        var waitable = FindFirstChangeNotification(directory.FullName, watchSubFolders, (uint)fileCreatedDeletedOrUpdated);
        var notifySize = Marshal.SizeOf(typeof(FileNotifyInformation));

        do
        {
            WaitForSingleObject(waitable, 0xFFFFFFFF); // Infinite wait
            var changes = new FileNotifyInformation[MaxChanges];
            var pinnedArray = GCHandle.Alloc(changes, GCHandleType.Pinned);
            var buffer = pinnedArray.AddrOfPinnedObject();
            uint bytesReturned;            

            ReadDirectoryChangesW(directoryHandle, buffer, (uint)(notifySize * MaxChanges), watchSubFolders, (uint)fileCreatedDeletedOrUpdated, out bytesReturned, IntPtr.Zero, IntPtr.Zero);

            for (var i = 0; i < bytesReturned / notifySize; i += 1)
            {
                var change = Marshal.PtrToStructure<FileNotifyInformation>(new IntPtr(buffer.ToInt64() + i * notifySize));

                if ((change.Action & (int)FileActions.FileAdded) == (int)FileActions.FileAdded)
                {
                    yield return new FileEvent(Marshal.PtrToStringAuto(change.FileName, change.FileNameLength), FileEventType.FileAdded);
                }
                else if ((change.Action & (int)FileActions.FileModified) == (int)FileActions.FileModified)
                {
                    yield return new FileEvent(Marshal.PtrToStringAuto(change.FileName, change.FileNameLength), FileEventType.FileChanged);
                }
                else if ((change.Action & (int)FileActions.FileRemoved) == (int)FileActions.FileRemoved)
                {
                    yield return new FileEvent(Marshal.PtrToStringAuto(change.FileName, change.FileNameLength), FileEventType.FileDeleted);
                }
                else if ((change.Action & (int)FileActions.FileRenamedNew) == (int)FileActions.FileRenamedNew)
                {
                    yield return new FileEvent(Marshal.PtrToStringAuto(change.FileName, change.FileNameLength), FileEventType.FileRenamed);
                }
            }

            pinnedArray.Free();
        } while (FindNextChangeNotification(waitable));

        FindCloseChangeNotification(waitable);
    }
}

var watcher = new DirectoryWatcher(@"C:\Temp");

foreach (var change in watcher.Watch())
{
    Console.WriteLine("File {0} was {1}", change.File.Name, change.EventType);
}

答案 2 :(得分:0)

我以前做过这个,但没有源代码(以前的工作),我遇到了同样的问题。我结束了创建一个BackgroundWorker实例,该实例将检查文件夹中的新文件。我会处理文件,然后将它们存档到子文件夹中。不确定这是否可能。

如果无法移动文件,BackgroundWorker可能仍然是答案。跟踪文件的LastModifiedDate或CreatedDate并处理任何更新的文件。在onCreated中,您将创建BackgroundWorker的实例并在文件上使用DoWork。处理时间为20秒,我假设您在onCreated事件逻辑中直接调用了所有逻辑。通过将它带到另一个线程,你可以做近乎即时的处理并完成,而另一个线程会一直搅拌直到它完成。