I am using this code to monitor creation of files in certain folder:
_watcher = new RecoveringFileSystemWatcher(SourceFolder, "*.xml");
_watcher.Created += (_, e) =>
{
ProcessFile(e.Name);
};
RecoveringFileSystemWatcher is a fileSystemWatcher wrapper. It's constructor is:
public RecoveringFileSystemWatcher (string path, string filter)
{
_containedFSW = new FileSystemWatcher(path, filter);
}
The process works as expected but for some files, randomly, an exception is thrown telling that the file is used by another process.
This is the method that is launched upon file creation:
var nfo = new FileInfo(filePath);
if (nfo.Exists)
{
var archivoXml = nfo.Name;
string archivo = String.Empty;
try
{
string content = Task.Run(async () => await GetFileContent(filePath)).Result;
if (String.IsNullOrEmpty(content))
return false;
XmlDocument xml = new XmlDocument();
xml.LoadXml(content);
//The rest of the method
}
}
the method GetFileContent is this:
private async Task<string> GetFileContent(string filePath)
{
string content = String.Empty;
try
{
Console.Write("ONE - "); InfoLog.Save($"ONE {filePath}");
using (StreamReader sr = new StreamReader(filePath))
{
Console.Write("TWO - "); InfoLog.Save($"TWO {filePath}");
content = await sr.ReadToEndAsync().ConfigureAwait(false);
Console.Write($"THREE {(sr.BaseStream == null ? "Closed" : "Opened")} - "); InfoLog.Save($"THREE {(sr.BaseStream == null ? "Closed" : "Opened")} {filePath}");
sr.Close();
Console.WriteLine($"FOUR {(sr.BaseStream == null ? "Closed" : "Opened")}"); InfoLog.Save($"FOUR {(sr.BaseStream == null ? "Closed" : "Opened")} {filePath}");
}
}
catch (Exception ex)
{
InfoLog.Save($"XML file could be read -> {filePath}. See error log.");
ErrorLog.Save(ex);
}
return content;
}
Look at the log information I am writing to debug the process.
I got one case with a file called 1112186.xml.... this is recorded in the log:
18/12/2018 19:12:10 ONE D:\GestorDocumental\Origen\1112186.xml
18/12/2018 19:12:10 XML file could not be read -> D:\GestorDocumental\Origen\1112186.xml. See error log.
As you see, the exception is thrown at the "using" instruction.
If I see the full log, I can see that file, 1112186.xml, is never used before, so the only chance is that FSW keeps the file opened. I don't know why, but it seems this is happening.
It is clear also that this process is locking the file, because when I exit the console application and then run again, the file can be processed.
Any help about this, please?
thanks
Jaime
I usually use this method to check if file is locked. I got it from one of the link in stackoverflow.
public static bool IsFileClosed(string filepath)
{
bool fileClosed = false;
int retries = 20;
const int delay = 400; // set a delay period = retries*delay milliseconds
if (!File.Exists(filepath))
return false;
do
{
try
{
// Attempts to open then close the file in RW mode, denying other users to place any locks.
FileStream fs = File.Open(filepath, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
fs.Close();
fileClosed = true; // success
}
catch (IOException) { }
retries--;
if (!fileClosed)
Thread.Sleep(delay);
}
while (!fileClosed && retries > 0);
return fileClosed;
}
This is a new class called FileTimerWatcher (it will have logger injected):
public FileTimerWatcher(ILogger logger) : base(logger)
{
if (timer == null)
{
// Create a timer with a 1.5 second interval.
// monitor the files after 1.5 seconds.
timer = new Timer(delay);
// Hook up the event handler for the Elapsed event.
timer.Elapsed += new ElapsedEventHandler(ProcessFolder);
timer.AutoReset = true;
timer.Enabled = true;
}
}
private void ProcessFolder(object sender, ElapsedEventArgs e)
{
var LastChecked = DateTime.Now;
string[] files = System.IO.Directory.GetFiles(SourceDirectory, somefilter, System.IO.SearchOption.TopDirectoryOnly);
foreach (string file in files)
{
ProcessFile(file); // process file here
}
}
Related
I am trying to efficiently open-close-reopen a power bi file (.pbix) from a WPF application button click. My method starts by creating a process that opens the pbix file then kills the process when the file is closed and then when the button is clicked again creates a new process to re-open the file.
Kindly find below the code I use to execute the steps above.
namespace TestApp
{
public class MainWindowViewModel : INotifyPropertyChanged
{
public int CheckFileIsOpen(string filenamepath)
{
try
{
using FileStream fs = new FileStream(filenamepath, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
return 0;
}
catch (Exception)
{
WindowEffect = new BlurEffect();
Mouse.OverrideCursor = null;
bool? Result = new CustomMessageBox($"File: {filenamepath.Split(#"\").Last()} in use!\nClose it and try again.", "File used by another process", MessageType.Error, MessageButtons.Ok).ShowDialog(); //this is a MessageBox object
if (Result.Value)
{
WindowEffect = null;
return 1;
}
else
{
WindowEffect = null;
return 2;
}
}
}
private void OpenOnlineLocally(bool open_local)
{
Process p = new Process();
string copy_name = "File_Copy.pbix";
string path = AppDomain.CurrentDomain.BaseDirectory; //the directory the .exe file runs.
try
{
Mouse.OverrideCursor = Cursors.Wait;
if (open_local == true)
{
int IsPBIFileOpen = CheckFileIsOpen($#"{path}{copy_name}");
if (new[] { 1, 2 }.Contains(IsPBIFileOpen))
{
return;
}
//Open the file using the system process
p.StartInfo = new ProcessStartInfo($"{path}{copy_name}")
{
UseShellExecute = true
};
p.Start();
}
else
{
OpenUrl("https://app.powerbi.com/...");
}
}
finally
{
if (p.HasExited) { p.Kill(); } //kill the process if the user closed the .pbix file
}
}
public ICommand ExportPowerBICommand //binded to a button click command in xaml
{
get { return new DelegateCommand<object>(FuncExportPowerBI); }
}
public void FuncExportPowerBI(object parameter)
{
Mouse.OverrideCursor = Cursors.Wait;
try
{
OpenOnlineLocally(true);
}
finally
{
Mouse.OverrideCursor = null;
}
}
}
}
The above code generates this error in the finally statement:
System.InvalidOperationException: 'No process is associated with this object.'
Some notes after experimentation:
The process should be killed when the user closes the .pbix file (i.e. clicks the X icon on top right corner of the desktop app). If the process is not killed and the user re-clicks the button to re-open the file then I get an error that the file is already opened and used by another process.
I prefer to avoid a solution that uses process.WaitForExit(), for two reasons. First, the application freezes while the file is used by the user. Second, it takes a couple of seconds for the desktop to realize that the process has exited so it can kill() it (not time efficient).
Since you're running .NET 5, there's an asynchronous method Process.WaitForExitAsync(). Async operation will not block the UI.
I've made the changes to two methods
private async Task OpenOnlineLocally(bool open_local)
{
Process p = new Process();
string copy_name = "File_Copy.pbix";
string dir = AppDomain.CurrentDomain.BaseDirectory; //the directory the .exe file runs.
string path = Path.Combine(dir, copy_name);
try
{
if (open_local == true)
{
int IsPBIFileOpen = CheckFileIsOpen(path);
if (IsPBIFileOpen != 0)
{
return;
}
//Open the file using the system process
p.StartInfo = new ProcessStartInfo(path)
{
UseShellExecute = true
};
p.Start();
await p.WaitForExitAsync();
}
else
{
OpenUrl("https://app.powerbi.com/...");
}
}
finally
{
if (!p.HasExited) { p.Kill(); } //kill the process if the user closed the .pbix file
}
}
public async void FuncExportPowerBI(object parameter)
{
Mouse.OverrideCursor = Cursors.Wait;
try
{
await OpenOnlineLocally(true);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message); // handle possible exception here
}
Mouse.OverrideCursor = null;
}
I'm using FileSystemWatcher to detect directory changes, and after that I read file content and insert it to database.
Here's my code:
private FileSystemWatcher _watcher;
public MainWindow()
{
try
{
InitializeComponent();
GetFiles();
//Task.Factory.StartNew(() => GetFiles())
// .ContinueWith(task =>
// {
// }, System.Threading.CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
}
catch(Exception ex)
{
//..
}
}
public bool GetFiles()
{
_watcher = new FileSystemWatcher(Globals.iniFilesPath, "*.ini");
_watcher.Created += FileCreated;
_watcher.IncludeSubdirectories = false;
_watcher.EnableRaisingEvents = true;
return true;
}
private void FileCreated(object sender, FileSystemEventArgs e)
{
try
{
string fileName = Path.GetFileNameWithoutExtension(e.FullPath);
if (!String.IsNullOrEmpty(fileName))
{
string[] content = File.ReadAllLines(e.FullPath);
string[] newStringArray = content.Select(s => s.Substring(s.LastIndexOf('=') + 1)).ToArray();
ChargingStationFile csf = new Product
{
Quantity = Convert.ToDecimal(newStringArray[1]),
Amount = Convert.ToDecimal(newStringArray[2]),
Price = Convert.ToDecimal(newStringArray[3]),
FileName = fileName
};
ProductController.Instance.Save(csf);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
If I run this code with CTRL+F5 I received this message:
But If I go with F5 (Debugging mode) than I receive this and not this error about cannot access process and item is sucessfully saved. This is confusing me really..
Should I dispose watcher? or something like that? Maybe I'm missing something here?
This is first time I'm using FileSystemWatcher, obliviously something is really wrong here..
P.S I've found out that this line is causing an exception:
string[] content = File.ReadAllLines(e.FullPath);
how come?
Thanks guys
Cheers
File.ReadAllLines() cannot access the file when it is open for writing in another application but you can use a FileStream and StreamReader instead.
Replace string[] content = File.ReadAllLines(e.FullPath); with the following code and you should be able to read the contents of the file regardless of whether it is open in another application:
List<string> content = new List<string>();
using (FileStream stream = new FileStream(e.FullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (StreamReader sr = new StreamReader(stream))
{
while (!sr.EndOfStream)
content.Add(sr.ReadLine());
}
As mention in this answer:
Most likely what is happening here is that the FileCreated event is
being raised and tries to process the file before is has been
completely written to disk.
So, you need to wait until the file has finished to copy. According to this other answer:
From the documentation for FileSystemWatcher:
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, a workaround for your case will be to create a list of strings containing the paths of the files that could not be read in the Created method handler, and re-process those paths in the Changed event of the FileSystemWatcher (read the comments in the code) :
public partial class MainWindow : Window {
private FileSystemWatcher _watcher;
public MainWindow() {
try {
InitializeComponent();
GetFiles();
} catch (Exception ex) {
MessageBox.Show($"Exception: {ex.Message}");
}
}
private bool GetFiles() {
_watcher = new FileSystemWatcher(#"C:\TestFolder", "*.ini");
_watcher.Created += FileCreated;
_watcher.Changed += FileChanged; // add this.
_watcher.IncludeSubdirectories = false;
_watcher.EnableRaisingEvents = true;
return true;
}
// this field is new, and contains the paths of the files that could not be read in the Created method handler.
private readonly IList<string> _waitingForClose = new List<string>();
private void FileChanged(object sender, FileSystemEventArgs e) {
if (_waitingForClose.Contains(e.FullPath)) {
try {
string[] content = File.ReadAllLines(e.FullPath);
string[] newStringArray = content.Select(s => s.Substring(s.LastIndexOf('=') + 1)).ToArray();
MessageBox.Show($"On FileChanged: {string.Join(" --- ", newStringArray)}");
// Again, process the data from the file to saving in the database.
// removing the path, so as not to reprocess the file..
_waitingForClose.Remove(e.FullPath);
} catch (Exception ex) {
MessageBox.Show($"Exception on FileChanged: {ex.Message} - {e.FullPath}");
}
}
}
private void FileCreated(object sender, FileSystemEventArgs e) {
try {
string fileName = Path.GetFileNameWithoutExtension(e.FullPath);
if (!String.IsNullOrEmpty(fileName)) {
string[] content = File.ReadAllLines(e.FullPath);
string[] newStringArray = content.Select(s => s.Substring(s.LastIndexOf('=') + 1)).ToArray();
MessageBox.Show($"On FileCreated: {string.Join(" --- ", newStringArray)}");
// process the data from the file to saving in the database.
}
} catch (Exception ex) {
// if the method fails, add the path to the _waitingForClose variable
_waitingForClose.Add(e.FullPath);
//MessageBox.Show($"Exception on FIleCreated: {ex.Message} - {e.FullPath}");
}
}
}
I wrote program that listens on a directory (GUI - WPF). When the new file show up in this directory is sent to the printer. The problem occurs when I try to save a large file to this directory. I have to wait until the file is closed, and then send it to the printer. I have a function that checks if the file is open. But when I use it in the whole GUI hangs. How do I use this function asynchronously?
protected void newfile(object fscreated, FileSystemEventArgs Eventocc)
{
try
{
string CreatedFileName = Eventocc.Name;
FileInfo createdFile = new FileInfo(CreatedFileName);
string extension = createdFile.Extension;
string eventoccured = Eventocc.ChangeType.ToString();
fsLastRaised = DateTime.Now;
this.Dispatcher.Invoke((Action)(() =>
{
String file = "";
file = watchingFolder + "\\" + CreatedFileName;
//printing
this.Dispatcher.Invoke((Action)(() =>
{
FileInfo info = new FileInfo(file);
while (!IsFileReady(info)) { }
var t = new Thread(() => printFile(file, extension)); //send to printer
t.Start();
}));
}));
}
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show("Error");
}
}
IsFileReady function:
public static bool IsFileReady(FileInfo file)
{
FileStream stream = null;
try
{
stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None);
}
catch (IOException)
{
//the file is unavailable because it is:
//still being written to
//or being processed by another thread
//or does not exist (has already been processed)
return true;
}
finally
{
if (stream != null)
stream.Close();
}
//file is not locked
return false;
}
And printfile
public void printFile(string filepath, string ext)
{
ProcessStartInfo info = new ProcessStartInfo();
info.Verb = "print";
info.FileName = filepath;
info.CreateNoWindow = true;
info.WindowStyle = ProcessWindowStyle.Hidden;
Process p = new Process();
p.StartInfo = info;
p.Start();
p.WaitForInputIdle();
System.Threading.Thread.Sleep(3000);
if (false == p.CloseMainWindow())
p.Kill();
}
}
How can I correct this code to work with large files without hangs up?
EDIT:
For check new file I use FileSystemWatcher
private void start(object sender, RoutedEventArgs e)
{
if (watchingFolder == null)
{
}
else
{
fs = new FileSystemWatcher(watchingFolder, "*.*");
fs.EnableRaisingEvents = true;
fs.IncludeSubdirectories = true;
fs.Created += new FileSystemEventHandler(newfile);
btnSatrt.IsEnabled = false;
btnStop.IsEnabled = true;
}
}
You're executing while (!IsFileReady(info)) { } through Dispatcher.Invoke, that executes the code on the UI thread so it will block for sure the app.
You aren't interacting at all with the UI, so the correct approach is to execute it asynchronously, via Tasks and awaits or with a background thread via the ThreadPool and not using at all Dispatcher.Invoke.
Try to execute all code in the newfile event handler on a background thread by starting a new task:
protected async void newfile(object fscreated, FileSystemEventArgs Eventocc)
{
try
{
await Task.Run(() =>
{
string CreatedFileName = Eventocc.Name;
FileInfo createdFile = new FileInfo(CreatedFileName);
string extension = createdFile.Extension;
string eventoccured = Eventocc.ChangeType.ToString();
fsLastRaised = DateTime.Now;
string file = watchingFolder + "\\" + CreatedFileName;
FileInfo info = new FileInfo(file);
while (!IsFileReady(info)) { }
printFile(file, extension);
});
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show("Error");
}
}
Use a BackgroundWorker instead of Dispatcher.Invoke.
I wrote the simplified version of my program below. Process A launches a child process (Process B). I use an anonymous pipe to write information about the progress of a method running on process B. Meanwhile I have a function in process A that continually reads from a stream to see if there is a new update coming in from the pipe. If there is, the form on process A is updated to reflect the progress. This works as expected, however I am wondering if there is a better way to accomplish this without having to continually check the stream to see if there are any new updates to the progress.
/////////////////
///Process A ////
/////////////////
public void LaunchProcessB()
{
using (AnonymousPipeServerStream pipeServer = new AnonymousPipeServerStream(PipeDirection.In,
HandleInheritability.Inheritable))
{
var _Process = new Process();
_Process.StartInfo.FileName = exeString;
_Process.StartInfo.Arguments = pipeServer.GetClientHandleAsString()
_Process.StartInfo.RedirectStandardOutput = true;
_Process.StartInfo.RedirectStandardInput = true;
_Process.StartInfo.CreateNoWindow = true;
_Process.StartInfo.UseShellExecute = false;
_Process.Start(); //launches process B
pipeServer.DisposeLocalCopyOfClientHandle();
using (StreamReader sr = new StreamReader(pipeServer))
{
try
{
while (true)
{
string temp = sr.ReadLine();
if (temp == null) break;
int result;
if (Int32.TryParse(temp, out result))
ShowDocumentProgress(result);
else ShowProgress(temp);
}
}
catch (Exception)
{
//error occured when reading from stream.
}
}
if (!_Process.Responding && !_Process.HasExited)
{
_Process.Kill();
return;
}
_Process.WaitForExit(10000);
}
}
private void ShowProgressPercent(int percentage)
{
if (percentage > currentPercentage)
{
progressBar.Value = percentage;
}
}
private void ShowProgress(string progressString)
{
labelMessage.Text = progressString;
}
/////////////////
///Process B ////
/////////////////
private StreamWriter _progressWriter;
private PipeStream _progressPipe;
static int Main(string[] args)
{
using (progressPipe = new AnonymousPipeClientStream(PipeDirection.Out, args[0]))
using (_progressWriter = new StreamWriter(_progressPipe))
{
RunLongProcess()
}
}
private void RunLongProcess()
{
//attaches events to PercentProgress and StageProgress methods.
}
private void PercentProgress(int percentage)
{
_progressWriter.WriteLine(percentage.ToString());
_progressPipe.WaitForPipeDrain();
}
private void StageProgress(string stage)
{
_progressWriter.WriteLine(stage);
_progressPipe.WaitForPipeDrain();
}
The while condition is not necessary. Simply read until temp is null. That's the end signal of the stream.
Make this a while(true) loop.
I think you also need to add exception handling to catch the process terminating and severing the pipe. !_Process.HasExited && pipeServer.IsConnected is not enough because it might be true but immediately switch to false after the test.
I also would add a WaitForExit at the end to make sure the system is quiesced before you continue.
I have a slight problem. What my application is supose to do, is to watch a folder for any newly copied file with the extention '.XSD' open the file and assign the lines to an array. After that the data from the array should be inserted into a MySQL database, then move the used file to another folder if it's done.
The problem is that the application works fine with the first file, but as soon as the next file is copied to the folder I get this exception for example: 'The process cannot access the file 'C:\inetpub\admission\file2.XPD' because it is being used by another process'.
If two files on the onther hand is copied at the same time there's no problem at all.
The following code is on the main window:
public partial class Form1 : Form
{
static string folder = specified path;
static FileProcessor processor;
public Form1()
{
InitializeComponent();
processor = new FileProcessor();
InitializeWatcher();
}
static FileSystemWatcher watcher;
static void InitializeWatcher()
{
watcher = new FileSystemWatcher();
watcher.Path = folder;
watcher.Created += new FileSystemEventHandler(watcher_Created);
watcher.EnableRaisingEvents = true;
watcher.Filter = "*.XPD";
}
static void watcher_Created(object sender, FileSystemEventArgs e)
{
processor.QueueInput(e.FullPath);
}
}
As you can see the file's path is entered into a queue for processing which is on another class called FileProcessor:
class FileProcessor
{
private Queue<string> workQueue;
private Thread workerThread;
private EventWaitHandle waitHandle;
public FileProcessor()
{
workQueue = new Queue<string>();
waitHandle = new AutoResetEvent(true);
}
public void QueueInput(string filepath)
{
workQueue.Enqueue(filepath);
if (workerThread == null)
{
workerThread = new Thread(new ThreadStart(Work));
workerThread.Start();
}
else if (workerThread.ThreadState == ThreadState.WaitSleepJoin)
{
waitHandle.Set();
}
}
private void Work()
{
while (true)
{
string filepath = RetrieveFile();
if (filepath != null)
ProcessFile(filepath);
else
waitHandle.WaitOne();
}
}
private string RetrieveFile()
{
if (workQueue.Count > 0)
return workQueue.Dequeue();
else
return null;
}
private void ProcessFile(string filepath)
{
string xName = Path.GetFileName(filepath);
string fName = Path.GetFileNameWithoutExtension(filepath);
string gfolder = specified path;
bool fileInUse = true;
string line;
string[] itemArray = null;
int i = 0;
#region Declare Db variables
//variables for each field of the database is created here
#endregion
#region Populate array
while (fileInUse == true)
{
FileStream fs = new FileStream(filepath, FileMode.Open, FileAccess.Read,
FileShare.ReadWrite);
StreamReader reader = new StreamReader(fs);
itemArray = new string[75];
while (!reader.EndOfStream == true)
{
line = reader.ReadLine();
itemArray[i] = line;
i++;
}
fs.Flush();
reader.Close();
reader.Dispose();
i = 0;
fileInUse = false;
}
#endregion
#region Assign Db variables
//here all the variables get there values from the array
#endregion
#region MySql Connection
//here the connection to mysql is made and the variables are inserted into the db
#endregion
#region Test and Move file
if (System.IO.File.Exists(gfolder + xName))
{
System.IO.File.Delete(gfolder + xName);
}
Directory.Move(filepath, gfolder + xName);
#endregion
}
}
The problem I get occurs in the Populate array region. I read alot of other threads and was lead to believe that by flushing the file stream would help...
I am also thinking of adding a try..catch for if the file process was successful, the file is moved to gfolder and if it failed, moved to bfolder
Any help would be awesome
Tx
You're not disposing of your FileStream instance, so a lock remains on the file. Change your code to use using blocks:
using (var fileStream = new FileStream(...))
{
using (var reader = new StreamReader(fileStream))
{
}
}
These using blocks will ensure the instances are correctly disposed of.
Also, why are you calling Flush on the file stream? You're not writing anything with it...
I would suggest :
1° use the using syntax on StreamReader
2° use the using syntax on FileStream