If I'm reading a text file in shared access mode and another process truncates it, what is the easiest way to detect that? (I'm excluding the obvious choice of refreshing a FileInfo object periodically to check its size) Is there some convenient way to capture an event? (Filewatcher?)
There is, It's called FileSystemWatcher.
If you are developing a windows forms application, you can drag-and-drop it from the toolbox.
Here's some usage example:
private void myForm_Load(object sender, EventArgs e)
{
var fileWatcher = new System.IO.FileSystemWatcher();
// Monitor changes to PNG files in C:\temp and subdirectories
fileWatcher.Path = #"C:\temp";
fileWatcher.IncludeSubdirectories = true;
fileWatcher.Filter = #"*.png";
// Attach event handlers to handle each file system events
fileWatcher.Changed += fileChanged;
fileWatcher.Created += fileCreated;
fileWatcher.Renamed += fileRenamed;
// Start monitoring!
fileWatcher.EnableRaisingEvents = true;
}
void fileRenamed(object sender, System.IO.FileSystemEventArgs e)
{
// a file has been renamed!
}
void fileCreated(object sender, System.IO.FileSystemEventArgs e)
{
// a file has been created!
}
void fileChanged(object sender, System.IO.FileSystemEventArgs e)
{
// a file is modified!
}
It's in System.IO and System.dll so you should be able to use it in most type of projects.
FSW cannot work reliably, it is asynchronous. Assuming you don't get an exception, StreamReader.ReadLine() will return null when the file got truncated. Then check if the size changed. Beware of the unavoidable race condition, you'll need to verify assumptions about timing.
Just something to chew on; it may not apply to your situation:
chakrit's solution is correct for what you asked for, but I have to ask -- why are you reading a file while another process truncates it?
In particular, if you don't have some synchronization, reading/writing files concurrently is not particularly safe, and you may find you have other mysterious problems.
Related
I build a windows-forms-app where I (try to) do extensive calculations on images whenever they are created in a specific directory which I watch using the FileSystemWatcher.
private void OnNewFileInDir(object source, FileSystemEventArgs evtArgs)
{
//Load the actual image:
imageFilepath = evtArgs.FullPath; //imageFilepath is a private class string var
Image currentImage = Image.FromFile(imageFilepath);
//Display the image in the picture box:
UpdatePictureBox(currentImage); //Method to update the GUI with invoking for the UI thread
//Extensive Calculation on the images
Image currentResultImage = DoExtensiveWork(currentImage);
// Put the current result in the picture box
UpdatePictureBox(currentResultImage );
//dispose the current/temporary image
currentImage.Dispose();
}
The event is fired correctly when pasting a new file into the directory. But I get a "System.OutOfMemoryException" on the line
Image currentImage = Image.FromFile(imageFilepath);
When I put exactly this code (using the same filepath) in a button event (so not using the FileSystemWatcher) everything works fine. So I thought there is some issue regarding the thread since the extensive calculation is then called by the FileSystemWatcher-Thread not by the UI thread.
I tried things like:
//TRY 1: By executing a button click method containg the code
pb_Calculate_Click(this, new EventArgs()); //This does not work eigther --> seems to be a problem with "Who is calling the method"
//TRY 2: Open a new dedicated thread for doing the work of the HistoCAD calculations
Thread newThread_OnNewFile = new Thread(autoCalcAndDisplay);
newThread_OnNewFile.Start();
//TRY 3: Use a background worker as a more safe threading method(?)
using (BackgroundWorker bw = new BackgroundWorker())
{
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
if (bw.IsBusy == false)
{
bw.RunWorkerAsync();
}
}
Unfortunalty none of them worked reliable. 1st not at all. 2nd works only from time to time and 3rd one as well.
Do some of you know whats going on there? What can I do to make it work correctly? Thanks!
EDIT:
Thanks for the comments:
I also tried to call GC.collect() on every event and tried to include using() and dispose() wherever I can. When I'm doing the process manually (with buttons) it works even when processing a lot of files one after another. But when done with the eventhandler I sometimes get the outOfMem-Exception even on the very first file I copy in the folder. File is always the same BMP with 32MB. This is the memory usage for processing one image:
EDIT 2:
I created a minimal example (GUI with one picture Box and one Checkbox in buttonstyle). It turns out that the same thing is happening. The OutOfMemException occured at the same line (Image...). Especially for large BMPs the exception occours nearly always:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace MinimalExampleTesting
{
public partial class Form1 : Form
{
private string imageFilepath;
private string autoModePath = #"C:\Users\Tim\Desktop\bmpordner";
//Define a filesystem watcher object
private FileSystemWatcher watcher;
public Form1()
{
InitializeComponent();
/*** Creating as FileSystemEventArgs watcher in order to monitor a specific folder ***/
watcher = new FileSystemWatcher();
Console.WriteLine(watcher.Path);
// set the path if already exists, otherwise we have to wait for it to be set
if (autoModePath != null)
watcher.Path = autoModePath;
// Watch for changes in LastAccess and LastWrite times and renaming of files or directories.
watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
| NotifyFilters.FileName | NotifyFilters.DirectoryName;
// Only watch for BMP files.
watcher.Filter = "*.bmp";
// Add event handler. Only on created, not for renamed, changed or something
// Get into the list of the watcher. Watcher fires event and "OnNewFileCreatedInDir" will be called
watcher.Created += new FileSystemEventHandler(OnNewFileInDir);
}
private void tb_AutoMode_CheckedChanged(object sender, EventArgs e)
{
//First of all test if the auto mode path is set and correctly exists currently:
if (!Directory.Exists(autoModePath) || autoModePath == null)
{
MessageBox.Show("Check if Auto Mode path is correctly set and if path exists",
"Error: Auto Mode Path not found");
return;
}
// Begin watching if the AutoModePath was at least set
if (autoModePath != null)
{
watcher.EnableRaisingEvents = tb_AutoMode.Checked; //Since we have a toogle butten, we can use the 'checked' state to enable or disable the automode
}
}
private void OnNewFileInDir(object source, FileSystemEventArgs evtArgs)
{
Console.WriteLine("New file in detected: " + evtArgs.FullPath);
//Force a garbage collection on every new event to free memory and also compact mem by removing fragmentation.
GC.Collect();
//Set the current filepath in the class with path of the file added to the folder:
imageFilepath = evtArgs.FullPath;
//Load the actual image:
Image currentImage = Image.FromFile(imageFilepath);
UpdatePictureBox(currentImage);
}
private void UpdatePictureBox(Image img)
{
if (pictureBox_Main.InvokeRequired)
{
MethodInvoker mi = delegate
{
pictureBox_Main.Image = img;
pictureBox_Main.Refresh();
};
pictureBox_Main.Invoke(mi);
}
else { //Otherwise (when the calculation is perfomed by the GUI-thread itself) no invoke necessary
pictureBox_Main.Image = img;
pictureBox_Main.Refresh();
}
img.Dispose();
}
}
}
Thanks in advance for further hints :)
SOLVED:
The issue seems to be, that event is fired immediately but the file is not yet finally copied. That means we have to wait until the file is free.
A Thread.Sleep(100) at the start of the event does the job. As I now know what to google for, I found two links:
This and this where you can find:
The OnCreated event is raised as soon as a file is created. If a file is being copied or transferred into a watched directory, the OnCreated event will be raised immediately, followed by one or more OnChanged events
So, what works best for my case, was to include a method to test if the file is still locked and than wait at the beginning of the event for an unlock of the file. No need for an additional thread or a BackgroundWorker.
See the code:
private void OnNewFileInDir(object source, FileSystemEventArgs evtArgs)
{
Console.WriteLine("New file detected: " + evtArgs.FullPath);
//Wait for the file to be free
FileInfo fInfo = new FileInfo(evtArgs.FullPath);
while (IsFileLocked(fInfo))
{
Console.WriteLine("File not ready to use yet (copy process ongoing)");
Thread.Sleep(5); //Wait for 5ms
}
//Set the current filepath in the class with path of the file added to the folder:
imageFilepath = evtArgs.FullPath;
//Load the actual image:
Image currentImage = Image.FromFile(imageFilepath);
UpdatePictureBox(currentImage);
}
private static bool IsFileLocked(FileInfo file)
{
FileStream stream = null;
try
{
//try to get a file lock
stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
}
catch (IOException)
{
//File isn't ready yet, so return true as it is still looked --> we need to keep on waiting
return true;
}
finally
{
if (stream != null){
stream.Close();
stream.Dispose();
}
}
// At the end, when stream is closed and disposed and no exception occured, return false --> File is not locked anymore
return false;
}
Nevertheless: Thanks for your help...it got me on the right track;)
As MSDN says about FileSystemWatcher:
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.
Maybe your Image is loaded severals times.
To test it, you can add this line after imageFilepath = evtArgs.FullPath;
imageFilepath = evtArgs.FullPath;
Task.Run(()=>{MessageBox.Show(imageFilepath);});
This will inform you about the fact that the Created event is fired, and will not hold up your program.
Edit
Put your line of code that give the OutOfMemory in a Try Catch.
Like this and this questions describes, you can get this error if your image is corrupt.
I have a serial port and a bunch of DataRecieved handler, I want one to be activated at some point if a certain condition is true in another handler.
For example
// write some message to StartCommHandler
private void StartCommHandler(object sender, SerialDataReceivedEventArgs e)
{
SerialPort sp = (SerialPort)sender;
byte[] startComResp = new byte[7];
if (sp.BytesToRead < 7)
return;
else
sp.DataReceived -= StartCommHandler;
sp.Read(startComResp, 0, startComResp.Length);
if ( //true response)
{
// enable StartMeasureHandler and write to it:
sp.DataReceived += StartMeasureHandler;
// write some message to StartCommHandler
}
else
{
// enable StartCommHandler and write to it again:
sp.DataReceived += StartCommHandler;
// write the message to StartCommHandler
}
// some other code that should not be read if else is selected
}
private void StartMeasureHandler(object sender, SerialDataReceivedEventArgs e)
{
//some code
}
when I enable the "StartMeasureHandler" and write to it, I expect that I should jump to it and the rest of the code should be ignored. But it continues reading the rest of the code in the "StartCommHandler" .
In other words, how should I jump out of one handler to another handler
p.s.: I know that I can put codes in if else statement, but I still need to know a solution to my issue to solve a more complicated problem.
Thanks
TL;DR: There's only one good way to deal with serial stream -- with a state machine. Make the compiler write that state machine for you and be happy.
Even better than swapping event handler is to not use the DataReceived event at all. Use sp.BaseStream.ReadAsync. Then you can write all your data handling code in blocking style which is easy, and the compiler will convert it to continuation-passing style, which performs well.
No threading to mess with. And it bypasses the most buggy parts of the SerialPort class.
I've written a blog post on the subject, which you can read for more details on why to avoid DataReceived and BytesToRead.
You should just be able to call
if ( /*true response*/)
{
sp.DataReceived += StartMeasureHandler;
StartMeasureHandler(object sender, SerialDataReceivedEventArgs e);
return;
}
// ...
It is still just a function.
To begin with, I'm relatively new to programming. I went through some introductory C# training for my new job, and it's the first language I've worked with.
I recently had a business problem that I decided to solve using C#, both to save time (I had hoped) and to learn more C# in the process. The business problem I mentioned was this: I had 600+ Word files that I needed to audit. For each document, I had to make sure that...
There was no text with strike-through anywhere in the document.
Track Changes was disabled.
There were no pending changes (as in changes that were made while
Track Changes was enabled and have yet to be accepted or
rejected).
There were no comments.
It would have been fastest to have my program iterate through all of the documents, making changes as it went along. But because of the nature of this assignment I wanted to make the changes manually, limiting the program's use to generating a list of files (out of the 600) where changes were necessary, and detailing what changes needed to be made for each of those files.
So, I have a button that calls up a FolderBrowserDialog.
private void AddFolderButtonClick(object sender, EventArgs e)
{
var folderBrowser = new FolderBrowserDialog();
if (folderBrowser.ShowDialog() != DialogResult.OK)
{
return;
}
this.progressBar1.Visible = true;
this.progressBar1.Style = ProgressBarStyle.Marquee;
this.Cursor = Cursors.WaitCursor;
var args = new List<string>(Directory.EnumerateDirectories(folderBrowser.SelectedPath));
// Get list of files in selected directory, adding to list of directories
args.AddRange(Directory.EnumerateFiles(folderBrowser.SelectedPath));
this.displayListBox.BeginUpdate();
foreach (string path in args)
{
if (File.Exists(path))
{
// This path is a file
this.ProcessFile(Path.GetFullPath(path));
}
else if (Directory.Exists(path))
{
// This path is a directory
this.ProcessDirectory((Path.GetFullPath(path)));
}
else
{
Console.WriteLine(Resources.Finder_Invalid_File_Or_Directory, path);
}
}
this.displayListBox.EndUpdate();
this.progressBar1.Visible = false;
this.progressBar1.Style = ProgressBarStyle.Continuous;
this.Cursor = Cursors.Default;
}
Together, the following two methods iterate through all subdirectories and files to create a full list of all files below the top level directory selected through the FolderBrowserDialog:
private void ProcessDirectory(string targetDirectory)
{
// Process the list of files found in the directory.
string[] fileEntries = Directory.GetFiles(targetDirectory);
foreach (string fileName in fileEntries)
{
this.ProcessFile(fileName);
}
// Recurse into subdirectories of this directory.
string[] subdirectoryEntries = Directory.GetDirectories(targetDirectory);
foreach (string subdirectory in subdirectoryEntries)
{
this.ProcessDirectory(subdirectory);
}
}
private void ProcessFile(string path)
{
Console.WriteLine(Resources.Finder_File_Processed, path);
string fileName = Path.GetFileName(path);
if (fileName == null || fileName.StartsWith(#"~$") || this.selectedFilesList.Contains(path))
{
return;
}
this.selectedFilesList.Add(path);
this.filePathsCountLabel.Text = (#"Count: " + this.selectedFilesList.Count);
this.displayListBox.Items.Add(path);
}
Once all this code has run, I get a full list of documents. I click a button and the program does what it's supposed to from here on out. Okay, cool. I mentioned before that half of the reason I chose to use C# to solve this was for the sake of learning. At this point I've got everything I need but what I really want to know is how can I implement threading to make the GUI responsive while the list of files is being generated? I've looked through several examples. They made sense. For some reason I just can't get my head around it for this application though. How can I make the whole process of processing subdirectories and files happen without locking up the GUI?
I believe what you need could be found here.
In short, to use a backgroundworker which does all the work on a separate thread thus prevents GUI freezes, first you instantiate BackgroundWorker and handle the DoWork event. Then you call RunWorkerAsync, optionally with an object argument.
As a skeleton code:
class myClass
{
static BackgroundWorker myBw = new BackgroundWorker();
static void Main()
{
myBw .DoWork += myBw_DoWork;
myBw .RunWorkerAsync ("an argument here");
Console.ReadLine();
}
static void myBw_DoWork (object sender, DoWorkEventArgs e)
{
// This is called on the separate thread, argument is called as e.Argument
// Perform heavy task...
}
}
You have to create a separate thread to process your work. Look at this if you are using .NET 4.0+ or this for older versions.
With Task, you can write
Task.Factory.StartNew(() => DoAction()
where DoAction is your function that starts to process data.
But do not forget to use Invoke, if you want to act with GUI from separate thread. For example, if you want to update some Label text from separate thread, you have to do this
label1.Invoke(() => label1.Text = "Some Text");
I have a folder. When a file copied to that folder I need to rename it. Hence I have used a System.IO.FileSystemWatcher to do that. And I have implemented a service to do it.
My code as follows.
private System.IO.FileSystemWatcher FSWatcherTest;
FSWatcherTest.Created += new FileSystemEventHandler(FSWatcherTest_Created);
--------
private void FSWatcherTest_Created(object sender, System.IO.FileSystemEventArgs e)
{
//Some code
File.Move(oldfilepath, newfilepath);
//some code
}
When I copy a text file from my local machine it is working finely. But when I copy I large file from the network this is not working. Error is when it is copping this will fired. So the file is not accessible to this method. But I’m wondering why this is fired while it’s copping.
I’m using VS 2008 and C# for this application.
Thanks in advance.
Hacky solution:
If there is no way to know when the file has been fully copied, you could keep trying to do it until it works.
Something like this:
private void FSWatcherTest_Created(object sender, System.IO.FileSystemEventArgs e)
{
FileMover(object sender, System.IO.FileSystemEventArgs e);
}
private void FileMover(object sender, System.IO.FileSystemEventArgs e)
{
try{
//Some code
File.Move(oldfilepath, newfilepath);
//some code
}
catch
{
//Call an asynchronous method that will wait 1 second then call FileMover again
//with the same arguments,
//a BackGroundWorker would be perfect for that job.
}
}
Just found this snippit
Dim F As Short = FreeFile()
FileOpen(F, sFile, OpenMode.Binary, OpenAccess.ReadWrite, OpenShare.LockReadWrite)
FileClose(F)
Would it be possible to wrap it in a 'WHILE' loop so it waits until the copy process is free?
Problem is that application closes without any error, VS stays opened.
I have multiple dynamically created FileSystemWatchers, all of them have eventhandler on "Created" event. So this eventhandler method looks like this :
void watcher_FileCreated(object sender, FileSystemEventArgs e)
{
FileInfo f1 = new FileInfo(e.FullPath);
filesDataGrid.Rows.Add(f1.Name);
foreach (TLPclass table in parameterForm.getParameters)
{
//uses some funcion form another class
}
}
Line which causes program to close is the one where I'm adding File name to DataGridView - filesDataGrid.Rows.Add(f1.Name);
Also runs OK without that line.
Weird thing is that application runs normally, when launched from .exe file in projects folder. I can't see error in my code, but I guess theres something awfully wrong with it, if it doesn't even show error message.
And - what are the most common reasons why program could just shut down with no warnings?
The FileSystemWatcher will trigger the events in a separate thread. The logic inside the event handlers will need to take that fact in consideration and perform any synchronization needed. So you'll need something like this:
private void watcher_FileCreated(object sender, FileSystemEventArgs e)
{
if (filesDataGrid.InvokeRequired)
{
filesDataGrid.Invoke((MethodInvoker)delegate { watcher_FileCreated(sender, e); });
}
else
{
FileInfo f1 = new FileInfo(e.FullPath);
filesDataGrid.Rows.Add(f1.Name);
foreach (TLPclass table in parameterForm.getParameters)
{
//uses some funcion form another class
}
}
}
Wrap your function with a Try{}catch (Exception ex) { } block.
i Would think it is because DataGridRow needs to be created with the variables from the FileSystemWatcher first.
e.g
DataGridViewRow row = filesDataGrid.NewRow();
row["columnname"] = f1.name;
filesDataGrid.Rows.Add(row);