How to read a text file continuously in Windows Service - c#

I am reading a text file in windows Service .As per my requirement i have to read the text file line by line and process the contents of the line and insert into the database.The nature of the text file is such that it is constantly getting updated.Sometimes in a span of minute 100 lines will get added and sometimes no.So there is no fixed rate at which lines are getting inserted into the text file.
At present i am reading the text file using StreamReader line by line with while loop but as soon as line gets over it comes back.Here is my structure of windows Service.
public partial class Service1 : ServiceBase
{
public Service1()
{
InitializeComponent();
}
private System.Threading.Thread _thread;
private ManualResetEvent _shutdownEvent = new ManualResetEvent(false);
public void OnDebug()
{
OnStart(null);
}
protected override void OnStart(string[] args)
{
_thread = new Thread(parseAndProcess);
_thread.Start();
}
public void parseAndProcess()
{
if (System.IO.File.Exists(FileToCopy) == true)
{
using (StreamReader reader = new StreamReader(FileToCopy))
{
string line;
while ((line = reader.ReadLine()) != null)
{
if (line != "")
{
//line processing logic goes here
}
reader.Close();
}
}
}
}
protected override void OnStop()
{
_shutdownEvent.Set();
_thread.Join(); // wait for thread to stop
}
}
}
This is the entire structure of my windows Service.Here reading stops if there is no next line in the text file available for that time and it will be stopped until the service is restarted which i dont want to do.
So how can i check for file updation in case streamreader gets stopped reading lines.
Here is my updated code with FileSystemWatcher
protected override void OnStart(string[] args)
{
_thread = new Thread(parseAndProcess);
_thread.Start();
FileSystemWatcher Watcher = new FileSystemWatcher("File Path");
Watcher.EnableRaisingEvents = true;
Watcher.Changed += new FileSystemEventHandler(Watcher_Changed);
}
// This event is raised when a file is changed
private void Watcher_Changed(object sender, FileSystemEventArgs e)
{
_thread = new Thread(parseAndProcess);
_thread.Start();
}
In this Code i have a doubt will my how to read only new lines added into the textfile when getting invoked by FileSystemWatcher Changed Event.
Please help.

Create a private copy of the file, that way the file is open to be changed by other processes.
Process the copy.
Delete the copy.
If the original file has changed (could use FileSystemWatcher) repeat and do not process lines that you already processed.
Detecting lines that you already processed might be the hardest but depends on the idempotency of the system and the actual content of the file.
Idempotency means that the system (database) you are feeding the lines to does not change when passing it the same line multiple times.
Recognizing the same line depends on the content of the line. It might be a line number, GUID, ID, Timestamp

Related

C# and the FileSystemWatcher

i have written a service in C# which should move backup files (*.bak and *.trn) from a database server to a special backup server. This works quite well so far. The problem is that it tries to move single files twice. This fails of course. I have configured FileSystemWatcher as follows:
try
{
m_objWatcher = new FileSystemWatcher();
m_objWatcher.Filter = m_strFilter;
m_objWatcher.Path = m_strSourcepath.Substring(0, m_strSourcepath.Length - 1);
m_objWatcher.IncludeSubdirectories = m_bolIncludeSubdirectories;
m_objWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.LastAccess; // | NotifyFilters.CreationTime;
m_objWatcher.Changed += new FileSystemEventHandler(objWatcher_OnCreated);
}
catch (Exception ex)
{
m_objLogger.d(TAG, m_strWatchername + "InitFileWatcher(): " + ex.ToString());
}
Is it possible that the Watcher produces an event twice for the same file? If I set the filter to CreationTime only, it does not react at all.
How do I have to set the Watcher to fire an event only once per file?
Thanks in advance for your help
The documentation states that common file system operations might raise more than one event. Check under the Events and Buffer Sizes heading.
Common file system operations might raise more than one event. For example, when a file is moved from one directory to another, several OnChanged and some OnCreated and OnDeleted events might be raised. Moving a file is a complex operation that consists of multiple simple operations, therefore raising multiple events. Likewise, some applications (for example, antivirus software) might cause additional file system events that are detected by FileSystemWatcher.
It also offers a few guidelines, including:
Keep your event handling code as short as possible.
To that end, you could use your FileSystemWatcher.Changed event to queue files for processing, then process them later. This is a quick example of what that might look like using an instance of System.Threading.Timer to process the queue.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
public class ServiceClass
{
public ServiceClass()
{
_processing = false;
_fileQueue = new ConcurrentQueue<string>();
_timer = new System.Threading.Timer(ProcessQueue);
// Schedule the time to run in 5 seconds, then again every 5 seconds.
_timer.Change(5000, 5000);
}
private void objWatcher_OnChanged(object sender, FileSystemEventArgs e)
{
// Just queue the file to be processed later. If the same file is added multiple
// times, we'll skip the duplicates when processing the files.
_fileQueue.Enqueue(e.FilePath);
}
private void ProcessQueue(object state)
{
if (_processing)
{
return;
}
_processing = true;
var failures = new HashSet<string>();
try
{
while (_fileQueue.TryDequeue(out string fileToProcess))
{
if (!File.Exists(fileToProcess))
{
// Probably a file that was added multiple times and it was
// already processed.
continue;
}
var file = new FileInfo(fileToProcess);
if (FileIsLocked(file))
{
// File is locked. Maybe you got the Changed event, but the file
// wasn't done being written.
failures.Add(fileToProcess);
continue;
}
try
{
fileInfo.MoveTo(/*Your destination*/);
}
catch (Exception)
{
// File failed to move. Add it to the failures so it can be tried
// again.
failutes.Add(fileToProcess);
}
}
}
finally
{
// Add any failures back to the queue to try again.
foreach (var failedFile in failures)
{
_fileQueue.Enqueue(failedFile);
}
_processing = false;
}
}
private bool IsFileLocked(FileInfo file)
{
try
{
using (FileStream stream = file.Open(FileMode.Open, FileAccess.Read,
FileShare.None))
{
stream.Close();
}
}
catch (IOException)
{
return true;
}
return false;
}
private System.Threading.Timer _timer;
private bool _processing;
private ConcurrentQueue<string> _fileQueue;
}
Credit where it's due, I took FileIsLocked from this answer.
Some other things you might need to consider:
What happens if your FileSystemWatcher misses an event? [The documentation] does state that it is possible.
Note that a FileSystemWatcher may miss an event when the buffer size is exceeded. To avoid missing events, follow these guidelines:
Increase the buffer size by setting the InternalBufferSize property.
Avoid watching files with long file names, because a long file name contributes to filling up the buffer. Consider renaming these files using shorter names.
Keep your event handling code as short as possible.
What happens if your service crashes, but the process writing backup files continues to write them? When you restart your service, will it pick those files up and move them?
I tried all sorts of ideas to stop this. The events are too close together... it can't be stopped in the FileChanged Event. Here is my working solution:
private System.Timers.Timer timer;
private FileSystemWatcher fwatcher;
static void Main(string[] args)
{
new Program();
}
private Program()
{
timer = new System.Timers.Timer(100);
timer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
timer.AutoReset = false; // only once
fwatcher = new FileSystemWatcher();
fwatcher.Path = filePath;
fwatcher.Filter = fileName;
fwatcher.NotifyFilter = NotifyFilters.LastWrite;
fwatcher.Changed += new FileSystemEventHandler(FileChanged);
fwatcher.EnableRaisingEvents = true;
while (IsRunning)
{
Thread.Sleep(100);
}
Thread.Sleep(100);
}
private void FileChanged(object sender, FileSystemEventArgs e)
{
timer.Start();
}
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
Console.WriteLine("file has changed!");
}
The timer will only fire once each time the file is changed.

How to properly use FileSystemWatcher in C# -- Multiple Files and Getting "Lazy" on me

What I'm trying to do is watch a directory for new folders that are added.
Once a folder is added, I copy a standard directory structure from a different folder into this one and then use Open Office XML to do a Search and Replace on the contents of one of the Microsoft Word documents that is included in the source folders.
All is working great... EXCEPT:
1) If I copy more than one folder to the "watched" directory, one of them will get handled, none of the others will get processed. Also, after that, the app gets "stuck" and needs to be restarted to work again.
2) For some reason, it occasionally gets "lazy" on me and just quits working. Watching the console window it shows that it's at the WaitForChanged part of the code, but nothing happens. This seems to happen inconsistently.
Here is the FileSystemWatcher portion of the code (don't want to bore you with the OOXML and folder copying bits):
class soClass
{
private const string strDirectory = #"C:\[DIRECTORY-TO-WATCH\]";
private static FileSystemWatcher fw = new FileSystemWatcher(strDirectory);
private static void WatchIt()
{
// WHILE TRUE -- JUST MAKES IT RUN OVER AND OVER AGAIN...
while (true)
{
Console.WriteLine("Waiting on file/folder changes...");
// HANGS HERE ON THE 'REG
string strName = fw.WaitForChanged(WatcherChangeTypes.Created, -1).Name;
Console.WriteLine("File/Folder Added!");
Console.WriteLine("Starting over...");
Console.WriteLine("--------------------------------------------------------");
}
}
}
As always, any help is greatly appreciated.
UPDATE:
Here is the final code that worked, thanks to #openshac
class soClass
{
private const string strDirectory = #"C:\[DIRECTORY-TO-WATCH]";
private static void Main()
{
WatchIt();
}
public static void WatchIt()
{
FileSystemWatcher fw = new FileSystemWatcher();
fw.Path = strDirectory;
fw.Created += new FileSystemEventHandler(OnCreated);
Console.WriteLine("Waiting on file/folder changes...");
// BEGIN WATCHING
fw.EnableRaisingEvents = true;
// WAIT FOR USER TO QUIT THE PROGRAM
Console.WriteLine("Press \'q\' to quit the sample.");
while (Console.Read() != 'q') ;
}
private static void OnCreated(object sender, FileSystemEventArgs fileSystemEventArgs)
{
Console.WriteLine("File/Folder Added!");
string strName = fileSystemEventArgs.Name;
// DO STUFF HERE!!
Console.WriteLine("Starting over...");
//rwlock.ExitWriteLock();
Console.WriteLine("--------------------------------------------------------");
}
}
Also found some help here:
https://msdn.microsoft.com/en-us/library/system.io.filesystemwatcher(v=vs.110).aspx
From the documenation:
This method waits indefinitely until the first change occurs and then
returns.
https://msdn.microsoft.com/en-us/library/67220zhk(v=vs.110).aspx
If you want to listen to multiple events try using the OnCreated event:
var fw = new FileSystemWatcher();
fw.EnableRaisingEvents = true;
fw.Created += OnCreated;
}
private static void OnCreated(object sender, FileSystemEventArgs fileSystemEventArgs)
{
var name = fileSystemEventArgs.Name;
}
Actually, it has been an issue for a while where it simply stops working unfortunately. The only methods I've seen to rectify this is to handle it and restart it.
More here: FileSystemWatcher stops catching events
Good luck. Hopefully one day they fix this.

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 (i.e. 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, i.e. 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 ...
}

Download file with backgroundWorker

i have created the downloader file
private void btnTestDownload_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
and work! but
private void btnTestDownload_Click(object sender, EventArgs e)
{
backgroundWorker1.CancelAsync();
}
the backgroundWorker dos not stop!
CancelAsync does not cause the worker to stop. You have to do that manually. Inside the worker method, you have to periodically check it's CancellationPending property to see if it should cancel or not.
So basically the body of the DoWork method should be something like this:
foreach( var something in somethingelse )
{
if (worker.CancellationPending == true) {
e.Cancel = true;
return;
}
//do your work here
}
If you inside your worker method just call some other method that itself takes a long time to complete, and you do not have the possibility to periodically check the CancellationPending variable yourself, then it's not easy to stop the worker on command without forcefully destroying the thread.
See MSDN:
When you call CancelAsync, your worker method has an opportunity to
stop its execution and exit. The worker code should periodically check
the CancellationPending property to see if it has been set to true.
It sounds like you've got completely the wrong idea about the purpose of a background worker. If you want to download a single file asyncronously with the ability to cancel it, all this functionality is built into the WebClient class.
Background worker is for long running tasks which are, on the whole, processor intensive. For example, if the file you were downloading were a large text file and you needed to parse each line of the text file, you could use the background worker for that, e.g.
Downloading a File
WebClient Client = new WebClient();
public void TestStart()
{
//Handle the event for download complete
Client.DownloadDataCompleted += Client_DownloadDataCompleted;
//Start downloading file
Client.DownloadDataAsync(new Uri("http://mywebsite.co.uk/myfile.txt"));
}
void Client_DownloadDataCompleted(object sender, DownloadDataCompletedEventArgs e)
{
//Remove handler as no longer needed
Client.DownloadDataCompleted -= Client_DownloadDataCompleted;
//Get the data of the file
byte[] Data = e.Result;
}
public void TestCancel()
{
Client.CancelAsync();
Client.DownloadDataCompleted -= Client_DownloadDataCompleted;
}
Processing a File
BackgroundWorker worker = new BackgroundWorker() { WorkerSupportsCancellation = true };
//Take a stream reader (representation of a text file) and process it asyncronously
public void ProcessFile(StreamReader Reader)
{
worker.DoWork += worker_DoWork;
worker.RunWorkerAsync(Reader);
}
public void CancelProcessFile()
{
worker.CancelAsync();
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
//Get the reader passed as an argument
StreamReader Reader = e.Argument as StreamReader;
if (Reader != null)
{
//while not at the end of the file and cancellation not pending
while (Reader.Peek() != -1 && !((BackgroundWorker)sender).CancellationPending)
{
//Read the next line
var Line = Reader.ReadLine();
//TODO: Process Line
}
}
}

How to save And delete document after CLOSE event?

I have a program that stores word documents in a database, and they are repeatedly opened, closed by the user and then saved back to the database.
when opening I place them on a temp folder.
but when closed, I want to save is back to the database and then delete it directly.
I tried this:
...
((DocumentEvents_Event)document).Close += DocumentClose;
...
delegate void MethodDelegate();
private void DocumentClose()
{
new MethodDelegate(deleteLater)();
}
void deleteLater()
{
//document.Close();
Thread.Sleep(1000);
File.Delete(this.tempDocFilePath);
}
but this don't work, and I get an error message telling me the file is already opened.
and when I uncomment "document.Close();" the next two lines are not excuted
any Ideas ?
This code is possibly subject to a race condition. NEVER trust a Sleep-solution. Wait for a specific event or poll and then take action.
Okay I solved it !
here's my code snippent:
private static Thread oThread = new Thread(new ParameterizedThreadStart(delete));
...
((DocumentEvents_Event)document).Close += DocumentClose;
...
private static void DocumentClose()
{
oThread.Start(path);
}
static void delete(object path)
{
try
{
File.Delete((string)path);
}
catch (Exception)
{
Thread.Sleep(500);
delete(path);
}
}

Categories