Windows服务中的TextFile Reader和FileSystemWatcher

时间:2014-07-25 07:28:25

标签: c# windows-services text-files filesystemwatcher

我试图在Windows Service中将任何内容更新到文本文件时使用FileSystemWatcher读取文本文件。现在我遇到的问题是没有得到我应该放置我的FileSystemWatcher代码的方式以便我会被调用一旦textfile被更改。我需要将其添加到OnStart() Windows服务方法或其他任何地方。

这是我的代码结构..

protected override void OnStart(string[] args)
    {
        _thread = new Thread(startReadingTextFile);
        _thread.Start();
    }

    public void startReadingTextFile() {
        _freader = new AddedContentReader(TextFileLocation);
    }
    private void Watcher_Changed(object sender, FileSystemEventArgs e)
    {
        string addedContent = _freader.GetAddedLines();
    }

请帮帮我。谢谢..

更新了代码..

 protected override void OnStart(string[] args)
    {
        if (lastLineReadOffset == 0)
        {
            _freader = new AddedContentReader(TextFileLocation);

        }
        //If you have saved the last position when the application did exit then you can use that value here to start from that location like the following
        //_freader = new AddedContentReader("E:\\tmp\\test.txt",lastReadPosition);
        else
        {
            _freader = new AddedContentReader(TextFileLocation, lastLineReadOffset);
        }

        FileSystemWatcher Watcher = new FileSystemWatcher("C:\\temp");
        Watcher.EnableRaisingEvents = true;
        Watcher.Changed += new FileSystemEventHandler(Watcher_Changed);
    }



    private void Watcher_Changed(object sender, FileSystemEventArgs e)
    {
        string addedContent = _freader.GetAddedLines();
        //you can do whatever you want with the lines
        using (StringReader reader = new StringReader(addedContent))
        {
            string line;
            while ((line = reader.ReadLine()) != null)
            {
                // Call the Processing Function
            }
        }

    }

2 个答案:

答案 0 :(得分:1)

  

我是否需要将其添加到OnStart()

但是,没有必要为此目的创建一个线程。设置FileSystemWatcher.EnableRaisingEvents后,将在线程池中触发事件:您可以从OnStart返回。

答案 1 :(得分:0)

理查德的答案是正确的。但是,Changed事件至少触发两次,因为默认情况下,FileSystemWatcher在创建文件时触发一次,然后在每次文件系统将其内容刷新到磁盘时再次触发。对于大文件,您可能会由于多次磁盘写入而导致多个Change事件。如果您是第一次尝试打开文件,则Change会在文件写入过程中被锁定或文件内容不完整时触发错误。

我发现的最可靠的方法是,在新文件的第一个Change事件上,以较短的间隔(几秒钟)设置一个计时器,然后在每次事件触发时,将其重置为相同时间文件。然后,在为文件触发最后一个Change事件几秒钟后触发定时器时,您将在计时器自己的Elapsed事件中打开文件。

这需要一些额外的代码和变量:

首先,创建一个Dictionary<string, Timer>来跟踪每个文件名的计时器。

在您的Change事件处理程序内部,您需要检查字典是否已经包含文件名作为键(在lock块内部以解决线程并发问题)。

  • 如果不是,则:
    • 创建一个新的Timer实例
    • 将其状态对象设置为文件名,以便在其Elapsed事件触发时,您将知道要处理的文件(使用闭包和lambda函数也可以实现相同的最终结果但是状态对象更简单)
    • 使用文件名作为关键字将新的计时器实例添加到字典中
  • 如果是(即,这不是该文件的第一个Change事件):
    • 在字典中查找Timer实例
    • 重置时间间隔以进一步推动其Elapsed事件

然后在计时器Elapsed事件的处理程序中,进行实际的处理和清理:

  • 从事件参数中传递的计时器的状态对象中获取文件名
  • 通过文件名在字典中查找Timer实例并进行处理
  • 从字典中删除计时器,即Remove(key),其中key是文件名(以上三个操作应在lock块内进行)
  • 打开文件并使用它做您需要做的一切。

以下是您可能希望在服务中实现此逻辑的方法:

    const int DELAY = 2000; // milliseconds
    const WatcherChangeTypes FILE_EVENTS = WatcherChangeTypes.Created | WatcherChangeTypes.Changed | WatcherChangeTypes.Renamed;

    FileSystemWatcher _fsw;
    Dictionary<string, Timer> _timers = new Dictionary<string, Timer>();
    object _lock = new object();

    public void Start()
    {
        _fsw = new FileSystemWatcher(Directory, FileFilter)
        {
            IncludeSubdirectories = false,
            EnableRaisingEvents = true
        };
        _fsw.Created += OnFileChanged;
        _fsw.Changed += OnFileChanged;
    }

    private void OnFileChanged(object sender, FileSystemEventArgs e)
    {
        try
        {
            // When a file is created in the monitored directory, set a timer to process it after a short
            // delay and add the timer to the queue.
            if (FILE_EVENTS.HasFlag(e.ChangeType))
            {
                lock (_lock)
                {
                    // File events may fire multiple times as the file is being written to the disk and/or renamed, 
                    // therefore the first time we create a new timer and then reset it on subsequent events so that 
                    // the file is processed shortly after the last event fires.
                    if (_timers.TryGetValue(e.FullPath, out Timer timer))
                    {
                        timer.Change(DELAY, 0);
                    }
                    else
                    {
                        _timers.Add(e.FullPath, new Timer(OnTimerElapsed, e.FullPath, DELAY, 0));
                    }
                }
            }
        }
        catch (Exception ex)
        {
            // handle errors
        }
    }

    private void OnTimerElapsed(object state)
    {
        var fileName = (string)state;
        lock (_lock)
        {
            try { _timers[fileName].Dispose(); } catch { }
            try { _timers.Remove(fileName); } catch { }
        }
        // open the file ...
    }