简体   繁体   中英

TextFile Reader and FileSystemWatcher in Windows Service

I am trying to use FileSystemWatcher to read a textfile as soon as anything gets updated into textfile in Windows Service.Now the problem that i am facing is not getting the way where i should put my FileSystemWatcher code so that i would get called as soon as textfile gets changed.Do i need to add this into OnStart() Method of Windows Service or anywhere else.

Here is my Code Structure..

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();
    }

Please help me .Thanks ..

Updated Code..

 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
            }
        }

    }

Do i need to add this into OnStart()

Yes.

But, there is no need to create a thread for this purpose. Once FileSystemWatcher.EnableRaisingEvents is set, then events will be fired in the thread pool: you can return from OnStart .

Richard's answer is correct. However, the Changed event fires at least twice because by default FileSystemWatcher fires it once when the file is created and then again every time the file system flushes its contents to the disk. For large files you may get multiple Change events caused by multiple disk writes. If you try to open the file the first time Change fires you may get errors if the file is locked by the writing process or get an incomplete file content.

The most reliable way I have discovered is to set a timer with a short interval (a few seconds) on the very first Change event for a new file and then reset it every subsequent time the event fires for the same file . You then open the file in the timer's own Elapsed event when it does fire a few seconds after the last Change event was fired for the file.

This requires some extra code and variables:

First, create a Dictionary<string, Timer> to keep track of timers per filename.

Inside your Change event handler you need to check if the dictionary already contains the file's name as a key (inside a lock block to take care of thread concurrency issues).

  • If it isn't then:
    • create a new Timer instance
    • set its state object to the name of the file so when its Elapsed event fires you'll know which file you are supposed to process (the same end result can also be achieved using a closure and a lambda function but a state object is simpler)
    • add the new timer instance to the dictionary using the file name as the key
  • If it is (ie this is not the first Change event for that file):
    • look up the Timer instance in the dictionary
    • reset its interval to push its Elapsed event further

Then in the handler of the timer's Elapsed event you do your actual processing and cleanup:

  • get the file name from the timer's state object passed in the event arguments
  • look up the Timer instance in the dictionary by the file name and dispose of it
  • remove the timer from the dictionary, ie Remove(key) where key is the file name (the three actions above should happen inside a lock block)
  • open the file and do whatever you need with it.

Here's how you might want to implement this logic inside your service:

    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 ...
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM