I want to refresh my datagridview if there are changes in a particular xml file. I got a FileSystemWatcher to look for any changes in the file and call the datagirdview function to reload the xml data.
When i tried, i'm getting Invalid data Exception error Somebody please tell what is the mistake am i doing here??
public Form1()
{
InitializeComponent();
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = #"C:\test";
watcher.Changed += fileSystemWatcher1_Changed;
watcher.EnableRaisingEvents = true;
//watches only Person.xml
watcher.Filter = "Person.xml";
//watches all files with a .xml extension
watcher.Filter = "*.xml";
}
private const string filePath = #"C:\test\Person.xml";
private void LoadDatagrid()
{
try
{
using (XmlReader xmlFile = XmlReader.Create(filePath, new XmlReaderSettings()))
{
DataSet ds = new DataSet();
ds.ReadXml(xmlFile);
dataGridView1.DataSource = ds.Tables[0]; //Here is the problem
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
private void Form1_Load(object sender, EventArgs e)
{
LoadDatagrid();
}
private void fileSystemWatcher1_Changed(object sender, FileSystemEventArgs e)
{
LoadDatagrid();
}
This is because the FileSystemWatcher runs on a distinct thread, not the UI thread. On winforms apps only the UI thread - the main thread of the program - can interact with visual constrols. If you need to interact with visual controls from another thread - like this case - you must call Invoke on the target control.
// this event will be fired from the thread where FileSystemWatcher is running.
private void fileSystemWatcher1_Changed(object sender, FileSystemEventArgs e)
{
// Call Invoke on the current form, so the LoadDataGrid method
// will be executed on the main UI thread.
this.Invoke(new Action(()=> LoadDatagrid()));
}
The FileSystemWatcher is running in a seperate thread and not in the UI thread. To maintain thread safety, .NET prevents you from updating the UI from the non-UI thread (i.e. the one that created the Form components).
To resolve the issue easily, call the MethodInvoker method of the target Form from your fileSystemWatcher1_Changed event. See MethodInvoker Delegate for more details on how to do this. There are other options on how to do this, incl. setting up a synchronized (i.e. thread-safe) object for holding the results/flag of any event, but this requires no changes to the Form code (i.e. in case of games, one could just poll the synchronized object in the main game loop etc).
private void fileSystemWatcher1_Changed(object sender, FileSystemEventArgs e)
{
// Invoke an anonymous method on the thread of the form.
this.Invoke((MethodInvoker) delegate
{
this.LoadDataGrid();
});
}
Edit: Corrected previous answer which had a problem within the delegate, the LoadDataGrid was missing this. and it would not resolve as such.
Related
I have a little problem with my WPF project and the FileSystemWatcher class.
In my MainWindow class the watcher begins to watch a folder when Button Start is clicked in the UI.
Everything works without any problems - the watcher recognizes correctly when a file is created.
But while watcher is waiting it is not possible for user to do anything in the UI. It should be possible for nexample to click Stop...
private void Start_Click(object sender, RoutedEventArgs e)
{
rdbTextBox.Document.Blocks.Clear();
Start.IsEnabled = false;
rdbTextBox.Document.Blocks.Add(new Paragraph(new Run("Test gestarte-Warte auf Befund....")));
Stop.IsEnabled = true;
watcher = new FileSystemWatcher(ConfigSettings.Default.FilePath);
// Only watch text files.
// watcher.Filter = "*.bef";
watcher.Filter = "*.txt";
// Add event handlers.
watcher.Created += OnCreated;
// Begin watching.
watcher.EnableRaisingEvents = true;
// Wait until new file in folder
watcher.WaitForChanged(System.IO.WatcherChangeTypes.Created);
watcher.Dispose();
// Parse letter
edifactLetter = parser.ParseDocument(ConfigSettings.Default.FilePath + "\\" + fileName);
// Validate Letter
edifactVal.Validate(edifactLetter);
writeResults();
Start.IsEnabled = true;
}
private void OnCreated(object sender, FileSystemEventArgs e)
{
FileInfo file = new FileInfo(e.FullPath);
fileName = file.Name;
}
Can anyone explain me what I am doing wrong?
Thanks!
This is because WaitForChanged() is not an asynchronous method, it is synchronous. Meaning if you are using that in your UI thread it will get blocked.
See here: https://msdn.microsoft.com/en-us/library/67220zhk(v=vs.110).aspx
I suggest that you could create an event handler for the OnChanged event and then do what you need to do.
Yes, WaitForChanged is a synchronous method:
This method waits indefinitely until the first change occurs and then returns.
You're calling that from the UI thread - therefore blocking any other UI thread interaction in the meantime. You don't want to do that.
You should probably just listen for the appropriate events - calling your parsing/validation methods in the event handler. You should also make sure you do all UI work in the UI thread, but ideally as little other work as possible... so unless the parsing and validation needs to interact with the UI, do it in a different thread.
You can mark method async and put long running task into a task. Don't forget prevent another call while first one is running:
private async void Start_Click(object sender, RoutedEventArgs e)
{
var button = (Button)sender;
button.Enabled = false;
await Task.Run(() =>
{
.. long running task here will not block UI
});
button.Enabled = true;
}
here it is the FULL source code : http://pastebin.com/mLaGwwi0
As you will notice , the thing is to scan directories and files and populate them in a tree view. I am using background worker (the same happens with Threads class) to do that in background, while updating treeview with nodes one by one.
The thing is that it is not working in background and not updating the treeview. This is the part of code which is hanging the form :
public void ListDirectory(DirectoryInfo path)
{
treeView1.Nodes.Add(CreateDirectoryNode(path));
}
public void Addnode(DirectoryInfo dirinfo)
{
Invoke(new AddCDAnode(ListDirectory), new object[] { dirinfo });
}
private TreeNode CreateDirectoryNode(DirectoryInfo directoryInfo)
{
var directoryNode = new TreeNode(directoryInfo.Name);
foreach (var directory in directoryInfo.GetDirectories())
{
//Statustext = directory.FullName;
directoryNode.Nodes.Add(CreateDirectoryNode(directory));
}
foreach (var file in directoryInfo.GetFiles())
directoryNode.Nodes.Add(new TreeNode(file.Name));
return directoryNode;
}
public delegate void AddCDAnode(DirectoryInfo dirinfo);
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
DirectoryInfo dir = new DirectoryInfo(#"\\server\trabajos 2013\");
Addnode(dir);
}
private void Form1_Load(object sender, EventArgs e)
{
//Start filling the TreeView on a separate thread
backgroundWorker1.RunWorkerAsync();
}
In full source code you will see commented code - that is from example and working in background without any troubles. So i think the problem is with my code of scanning directories.
The solution is also available here, if you want to download it.
The problem is that you start a background thread, but then immediately go and call Invoke to run basically all of your real code in the UI thread, consequently the UI thread is being blocked.
What you need to do is separate out your UI logic from your non-UI logic. Ensure that the non-UI logic is executed in the background thread, and that the UI logic is executed in the UI thread.
In this case, CreateDirectoryNode(path) is basically all of your non-UI work. It's doing a whole bunch of file manipulation (which is time consuming) and in general creating the data that your UI will later use.
That should be done in the DoWork event of your background worker. Then you can set the Result property of the BGW based on the results it generated so that it can be used later.
For the UI portion, which is the treeView1.Nodes.Add( call, that should be in the RunWorkerCompleted event. That event is designed for you to manipulate the UI based on the results of the long running task. There you can read in the Result property of the BGW (it is a property in the arguments passed to the event handler) and there it can add the data to the tree view. Since this is in the UI thread you won't get cross thread exceptions, and since your long running file IO is done in the background thread it won't hand the UI.
So all you need is your CreateDirectryNode method and the following:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
DirectoryInfo dir = new DirectoryInfo(#"\\server\trabajos 2013\");
e.Result = CreateDirectoryNode(dir);
}
//TODO ensure that this event handler is added to your BGW so it actually fires
void Form1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
treeView1.Nodes.Add((TreeNode)e.Result);
}
How can I fix this issue ?
I am expecting the progressbar to load during process untill process it is done
Here is my code:
private void btnProcess_Click(object sender, EventArgs e)
{
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.ProgressChanged += backgroundWorker_ProgressChanged;
backgroundWorker.DoWork += backgroundWorker_DoWork;
backgroundWorker.RunWorkerAsync();
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//start transaction
DoTransaction();
}
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Value = e.ProgressPercentage;
}
My transaction function:
private void DoTransaction()
{
string pathIdentifier;
pathIdentifier = func.checkthePathFile();
if (pathIdentifier == null)
{
MessageBox.Show("Path has no yet been specified!");
}
else
{
//create xml base from user inputs
XElement transactXML = new XElement("Transaction",
new XElement("CardNumber", txtCardNum.Text.Trim()),
new XElement("ExpireDate", txtExpDate.Text.Trim()),
new XElement("Cardtype", txtCardType.Text.Trim())
);
//save xml to a file
transactXML.Save(pathIdentifier + "/sample.xml");
}
}
How is the runtime supposed to know how far along your process is?
You need to tell it by calling backgroundWorker.ReportProgress from the background operation. No magic here.
MSDN: http://msdn.microsoft.com/en-us/library/ka89zff4.aspx
Break down your process into meaningful chunks and ReportProgress whenever it makes sense to do so.
public void DoTransaction()
{
part1();
backgroundWorker.ReportProgress(25);
part2();
backgroundWorker.ReportProgress(50);
part3();
backgroundWorker.ReportProgress(75);
part4();
backgroundWorker.ReportProgress(100);
}
Edit Based on Posting of Transaction() function
If you are not confident in writing multithreaded programs, then do not attempt to write multithreaded programs, even with the help of a BackgroundWorker which tries to abstract some of those details away from you.
A few issues:
Your provided Transaction() method attempts to launch a MessageBox and read the Text property of various controls from the background thread. This is going to cause problems as the runtime typically throws an Exception when UI elements are accessed from a thread other than the one which created them.
If you really want to do the XML saving in the BackgroundWorker, you should validate the filename and directory, and save the Text properties to an intermediate object before setting up the BackgroundWorker and calling RunWorkerAsync.
Furthermore, in my opinion, your Transaction method is not going to be time intensive enough to truly warrant a background thread. Even a relatively old PC will be able to create and save a 15 element XML file faster than you can blink. The runtime will probably waste more time marshalling data between the threads than it would to simply write the file out to disk. Just do your work in the button click event handler.
needs some reference to the BackgroundWorker instance.pass the reference to the class when instantiating it.
instantiate like this
BackgroundWorker worker = sender as BackgroundWorker;
then call like this
`worker.ReportProgress(...)`
Hi I have a problem using Filesystemwatcher & BackgroundWorker process.
I have a windows forms application that checks for new text files on a folder, it process them and creates xml files from them.
I´m using FSW to monitor for new txt files on a folder, the app works fine but when the folder receives a large amount of files (let's say 1000), the app freezes because it's processing all of them.
it occurred to me to add a backgroundworker, so the FSW calls it everytime a new file is created, this way we can process the file on the background without freezing the UI.
This idea did not work because for every file that is created, I try to call the RunWorkerAsync() method, so if it's busy processing a file and I try to process a new one it will throw the following error:
"This BackgroundWorker is currently busy and cannot run multiple tasks concurrently."
So I tried to loop the method with a while til it gets available but, infinite exception is thrown.
this is the simplified version of my code:
private void fileSystemWatcher1_Created(object sender, System.IO.FileSystemEventArgs e)
{
readFile();
}
private void readFile()
{
while (backgroundWorker1.IsBusy)
{
readFile();
}
backgroundWorker1.RunWorkerAsync(idx);
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
int i = (int)e.Argument;
i += 1;
e.Result = i;
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Text = "Processing...";
this.Refresh();
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
label1.Text = "Completed...";
this.Refresh();
idx = (int)e.Result;
}
The exception thrown says “An unhandled exception of type 'System.StackOverflowException' occurred in WindowsFormsApplication2.exe, make sure you do not have an infinite loop or recursion”
Of course I could remove the FSW, but I'd like to know if there's a way to make them work together, any ideas?
What you have is a classic Producer/Consumer problem.
Solve it with a System.Collections.Concurrent.ConcurrentQueue<string>.
On the FSW event, add the filename to the Queue.
Start 1 or 2 BackgroundWorkers to process the queue.
And this is the code that overflows your stack in a hurry:
private void readFile()
{
while (backgroundWorker1.IsBusy)
{
readFile(); // the recursive call, will fail quickly
}
backgroundWorker1.RunWorkerAsync(idx);
}
Not only does this cause an SO exception, it also blocks your main thread.
You need a better way to wait, and the ConcurrentQueue gives you that.
Instantiating new BackgroundWorkers would do the trick, as would Henk's solution above.
Or, you can do it without changing your code too much just using the ThreadPool.
private void fileSystemWatcher1_Created(object sender, System.IO.FileSystemEventArgs e)
{
ThreadPool.QueueUserWorkItem(o => readFile(e));
}
public void readFile(System.IO.FileSystemEventArgs e)
{
this.BeginInvoke(new MethodInvoker(() =>
{
label1.Text = "Processing...";
this.Refresh(); //you shouldn't need this
}));
//your long running read/processing... doing something event args
this.BeginInvoke(new MethodInvoker(() =>
{
label1.Text = "Completed...";
this.Refresh();
idx = (int) e.Result;
}));
}
Why not instantiate a new BackgroundWorker in readFile instead of reusing?
I have been working on a tool that uses a BackgroundWorker to perform a ping operation on a regular interval. I am running into an issue with the BackgroundWorker ProgressChanged event. The code for the ProgressChanged Event is below:
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
ProgressUpdated update = (ProgressUpdated)e.UserState;
if (sender.ToString() == "System.ComponentModel.BackgroundWorker")
{
toolStripStatusLabel1.Text = update.GeneralStatus;
toolStripProgressBar1.Value = update.ProgressStatus;
toolStripStatusLabel2.Text = update.SpecificStatus;
}
else
{
toolStripStatusLabel1.Text = update.GeneralStatus;
toolStripProgressBar2.Value = update.ProgressStatus;
toolStripStatusLabel3.Text = update.SpecificStatus;
}
}
The ProgressChanged event gets called both in the BackgroundWork where it updates the first values and from the pingcompletedcallback event when a ping finishes. I only run into the cross threading issue when the ProgressChanged event runs from the PingCompletedCallback event. It throws the error when it goes to update the second Progress bar.
I can not seem to figure out why its happening for one of the calls but not the other.
Is the PingCompletedCallBack happening on the BackgroundWorker thread and thats why its causing the cross threading issues?
If so how do I raise the event so that it will be processed on the UI thread and not the backgroundworker?
Edit:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
// creates ping and sends it async
ProgressUpdated args = new ProgressUpdated(string1, int1, string 2);
worker.ReportProgress(0,args);
// rest of thread for cleanup when cancellation is called
}
private void PingCompletedCallback(object sender, PingCompletedEventArgs e)
{
// handle the ping response
ProgressUpdated update = new ProgressUpdated(string1, int1, string2);
ProgressChangedEventArgs changed = new ProgressChangedEventArgs(1,update);
backgroundWorker1_ProgressChanged(this, changed);
// handle other types of responses
}
I thought the use of events was to allow the separation of threads. Aka worker thread raises an event that the UI thread is listening for, then the raised event gets processed on the UI thread.
Since my understanding was wrong, would the PingCompletedCallBack have access to the the ReportProgress method of the backgroundworker?
I could then change in PingCompletedCallback:
ProgressChangedEventArgs changed = new ProgressChangedEventArgs(1,update);
backgroundWorker1_ProgressChanged(this, changed);
to:
backgroundWorker1.ReportProgress(1, update);
or would I need to change it in some other way?
Thanks for anyone's assistance.
Edit 2:
Changed ProgrssChanged event
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
ProgressUpdated update = (ProgressUpdated)e.UserState;
toolStripStatusLabel1.Text = update.GeneralStatus;
toolStripProgressBar1.Value = update.ProgressStatus;
toolStripStatusLabel2.Text = update.SpecificStatus;
}
I then created a second update event
private void PingUpdate (object sender, ProgressUpdated e)
{
toolStripStatusLabel1.Text = e.GeneralStatus;
toolStripProgressBar2.Value = e.ProgressStatus;
toolStripStatusLable3.Text = e.SepcificStatus;
}
The only thing I have left is to call the new event from PingCompletedCallback in such a way as it gets executed on the UI Thread. Is this where the Invoke statement would be used or should the Invokes be used in the new event?
The documentation for BackgroundWorker states that you should not be manipulating UI objects through the DoWork method, and that any changes to UI objects should be made through ReportProgress. I haven't looked at reflector, but it's probably performing a hidden "Invoke" for you. Whatever is raising your PingCompleted event is probably executing within the worker thread or some other thread that is not the main thread.
You will see in the threads window of the Visual Studio debugger that DoTask does not execute on the main thread; however, when ReportProgress is called, the handler is executed on the main thread. Since your controls were probably created on the main thread, you do not see the exception.
Now, if you attempt to call backgroundWorker1_ProgressChanged explicitly within the DoWork method, then backgroundWorker1_ProgressedChanged will be executed on the same thread that's executing the DoWork method, or, in your case, the method that's raising the PingCompleted event:
You can probably solve this cross thread exception by adding InvokeRequired checks within your backgroundWorker1_ProgressChanged handler, or route your PingCompleted handler to call ReportProgress
EDIT:
Calling ReportProgress from the PingCompleted handler won't work because you will lose the original sender.
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (InvokeRequired)
{
Invoke(new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged), sender, e);
return;
}
// The rest of your code goes here
}
EDIT 2 Response:
private void PingUpdate (object sender, ProgressUpdated e)
{
if (InvokeRequired)
{
Invoke(new Action<object, ProgressUpdated>(PingUpdate), sender, e);
return;
}
toolStripStatusLabel1.Text = e.GeneralStatus;
toolStripProgressBar2.Value = e.ProgressStatus;
toolStripStatusLable3.Text = e.SepcificStatus;
}