FileSystemWatcher and GUI - c#

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

Related

C# executing code for GUI before executing blocking processes

I have (hopefully) a straight forward question. I have a function that runs a command prompt command in a hidden window and returns the response in a string. This process takes about 3 seconds. I wanted to add a simple label in my GUI that would appear before the function executes. The label just states that something is being checked so the user does not think the interface is just slow or unresponsive.
Here is an example snippet to illustrate.
svnPathCheck_lbl.Visible = true; //Show the label
// Check validity of SVN Path
string svnValidity = getCMDOutput("svn info " + SVNPath_txtbox.Text);
// Here we call Regex.Match. If there is a 'Revision:' string, it was successful
Match match = Regex.Match(svnValidity, #"Revision:\s+([0-9]+)", RegexOptions.IgnoreCase);
svnPathCheck_lbl.Visible = false; //Hide the label
The getCMDOutput() function runs the hidden command and blocks the GUI.
What I expected this to do was display my label "Checking ...", then run the blocking function getCMDOutput(). Once the function returned and the GUI was responsive again, it would hide the label.
Instead, I never see the label show up at all. Its almost like it never executed. Could it be that the blocking function executes before the GUI has a chance to update?
Thanks for the help!
try this code, it should work...
private void button1_Click(object sender, EventArgs e)
{
svnPathCheck_lbl.Text = "Checking...";
svnPathCheck_lbl.Visible = true;
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += bw_DoWork;
bw.RunWorkerCompleted += bw_WorkCompleted;
bw.RunWorkerAsync();
}
private void bw_WorkCompleted(object sender, RunWorkerCompletedEventArgs e)
{
svnPathCheck_lbl.Text = "Work completed";
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
string svnValidity = getCMDOutput("svn info " + SVNPath_txtbox.Text);
Match match = Regex.Match(svnValidity, #"Revision:\s+([0-9]+)", RegexOptions.IgnoreCase);
}
I recommend that you run your getCMDOutput method asynchronously.
If this is a windows forms application you can do this using a BackgroundWorker. Handle the event DoWork of the worker to call your method, and where you were calling it previously, put instead backgroundWorker1.RunWorkerAsync()
This will cause the method to run in a new thead, so the UI updates will be treated separately and will be instantaneous.
try this:
svnPathCheck_lbl.Visible = true; //Show the label
Task connectToSVN = new Task(() => { this.connectToSVN; }); connectToSVN.Start(); //Open new Task to complite the code without blocking the GUI.
private void connectToSVN
{
// Check validity of SVN Path
string svnValidity = getCMDOutput("svn info " + SVNPath_txtbox.Text);
// Here we call Regex.Match. If there is a 'Revision:' string, it was successful
Match match = Regex.Match(svnValidity, #"Revision:\s+([0-9]+)", RegexOptions.IgnoreCase);
this.Dispatcher.Invoke((Action)(() =>
{
svnPathCheck_lbl.Visible = false; //Hide the label
}
));
}

WPF - Cannot change a GUI property inside the OnChanged method (fired from FileSystemWatcher)

I want to change a GUI property in the OnChanged method... (in actuality im trying to set an image source.. but used a button here for simplicity). This is called everytime filesystemwatcher detects a change in a file.. and it gets to the "top" output.. but catches an exception when it tries to set the button width.
but if i put the same code in a button.. it works just fine. I sincerely don't understand why.. can someone help me?
private void OnChanged(object source, FileSystemEventArgs e)
{
//prevents a double firing, known bug for filesystemwatcher
try
{
_jsonWatcher.EnableRaisingEvents = false;
FileInfo objFileInfo = new FileInfo(e.FullPath);
if (!objFileInfo.Exists) return; // ignore the file open, wait for complete write
//do stuff here
Console.WriteLine("top");
Test_Button.Width = 500;
Console.WriteLine("bottom");
}
catch (Exception)
{
//do nothing
}
finally
{
_jsonWatcher.EnableRaisingEvents = true;
}
}
what i'm really trying to do instead of changing a button width:
BlueBan1_Image.Source = GUI.GetChampImageSource(JSONFile.Get("blue ban 1"), "avatar");
The problem is that this event is raised on a background thread. You need to marshal the call back to the UI thread:
// do stuff here
Console.WriteLine("top");
this.Dispatcher.BeginInvoke(new Action( () =>
{
// This runs on the UI thread
BlueBan1_Image.Source = GUI.GetChampImageSource(JSONFile.Get("blue ban 1"), "avatar");
Test_Button.Width = 500;
}));
Console.WriteLine("bottom");
I'm guessing that the FileSystemWatcher is calling your event handler on another thread. Inside your event handler, use your application's Dispatcher to marshal it back to the UI thread:
private void OnChanged(object source, FileSystemEventArgs e) {
Application.Current.Dispatcher.BeginInvoke(new Action(() => DoSomethingOnUiThread()));
}
private void DoSomethingOnUiThread() {
Test_Button.Width = 500;
}

FileSystemWatcher Invalid data Exception error

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.

This BackgroundWorker is currently busy and cannot run multiple tasks concurrently

I'm trying to use a Background Worker in a WPF application. The heavy lifting task uses WebClient to download some HTML and parse some info out of it. Ideally I want to do that downloading and parsing without locking the UI and placing the results in the UI once it's done working.
And it works fine, however, if I quickly submit the "download and parse" command, I get the error:
This BackgroundWorker is currently busy and cannot run multiple tasks
concurrently
So I did some Googling and it seems that I can enable the .WorkerSupportsCancellation property of the background worker and just .CancelAsync(). However, this doesn't work as expected (canceling the current download and parse).
I still get the above error.
Here's my code:
//In window constructor.
_backgroundWorker.WorkerSupportsCancellation = true;
_backgroundWorker.DoWork += new DoWorkEventHandler(_backgroundWorker_DoWork);
_backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_backgroundWorker_RunWorkerCompleted);
//Declared at class level variable.
BackgroundWorker _backgroundWorker = new BackgroundWorker();
//This is the method I call from my UI.
private void LoadHtmlAndParse(string foobar)
{
//Cancel whatever it is you're doing!
_backgroundWorker.CancelAsync();
//And start doing this immediately!
_backgroundWorker.RunWorkerAsync(foobar);
}
POCOClassFoo foo = new POCOClassFoo();
void _backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//This automagically sets the UI to the data.
Foo.DataContext = foo;
}
void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//DOING THE HEAVY LIFTING HERE!
foo = parseanddownloadresult()!
}
Calling CancelAsync will still fire the RunWorkerCompleted event. In this event, you need to make sure that CancelAsync has not been called, by checking e.Cancelled. Until this event fires, you cannot call RunWorkerAsync.
Alternatively, I would recommend you do what Tigran suggested and create a new BackgroundWorker each time.
Further more, I would recommend storing the results of_backgroundWorker_DoWork in e.Result, then retrieve them from the same in _backgroundWorker_RunWorkerCompleted
Maybe something like this
BackgroundWorker _backgroundWorker;
private BackgroundWorker CreateBackgroundWorker()
{
var bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = true;
bw.DoWork += _backgroundWorker_DoWork;
bw.RunWorkerCompleted += new _backgroundWorker_RunWorkerCompleted;
return bw.
}
private void LoadHtmlAndParse(string foobar)
{
//Cancel whatever it is you're doing!
if (_backgroundWorer != null)
{
_backgroundWorker.CancelAsync();
}
_backgroundWorker = CreateBackgroundWorker();
//And start doing this immediately!
_backgroundWorker.RunWorkerAsync(foobar);
}
//you no longer need this because the value is being stored in e.Result
//POCOClassFoo foo = new POCOClassFoo();
private void _backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
//Error handling goes here.
}
else
{
if (e.Cancelled)
{
//handle cancels here.
}
{
//This automagically sets the UI to the data.
Foo.DataContext = (POCOClassFoo)e.Result;
}
}
private void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//DOING THE HEAVY LIFTING HERE!
e.Result = parseanddownloadresult()!
}
The thing is that CancelAsync() does what it climes: cancel in async way. That means that it will not stop immediately, but after some time. That time can never be calculated or predicted, so you have a couple of options:
Wait until this backround worker stops really, by waiting in cycle until IsBusy property of it becomes false
Or, I think, better solution is to start another background worker, considering that request of cancelation was already sent to the first one, so it will be soon or later stop. In this case, you need to know from which background worker data comes, in order to process it or not, cause on start of second the first one will still run and pump the data from WebService.
Hope this helps.
CancelAsync returns before the worker cancels and stops its work. Hence, your RunWorkerAsync call is starting before the worker is ready, and you're getting that error. You'll need to wait for the worker to be ready first.
When I'm not interested in tracking progress of an async operation, I tend to prefer to just slap a lambda at ThreadPool.QueueUserWorkItem instead of instantiating and setting up a background worker that I have to check the state of to be able to reuse in a sane way.
You need to verify before you kicks in.
f( !bw.IsBusy )
bw.RunWorkerAsync();
else
MessageBox.Show("Can't run the bw twice!");
You are calling CancelAsync without waiting for the background worker to actually cancel the work. Also you must have your own logic for cancelling the work. There is a good example on MSDN which shows how to do it. Basically in your parseanddownloadresult() method you need to check the CancellationPending property.

WPF application in a loop, how to not have the whole application freeze?

I am having fun with WPF and got a problem. I have googled and found this website that has the same problem of me but without any working solution.
The problem is that I have a button that do some processing of data (around 30 sec). I want to have the button to disable and to have log writing in a text box... the problem is that it doesn't disable and it doesn't wrote any thing on the textbox until the processing is completely done.
Any idea?
private void button1_Click(object sender, RoutedEventArgs e)
{
this.button1.IsEnabled = false;
//Long stuff here
txtLog.AppendText(Environment.NewLine + "Blabla");
//End long stuff here
this.button1.IsEnabled = true;
}
As others have said, use the BackgroundWorker or some other method of doing work asychronously.
You can declare it under your Window, initialize it somewhere like the Loaded event, and use it in the Click event. Here's your method, modified to use BackgroundWorker, assuming you've declared it under the Window as _bw:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
_bw = new BackgroundWorker();
_bw.DoWork += new DoWorkEventHandler((o, args) =>
{
//Long stuff here
this.Dispatcher.Invoke((Action)(() => txtLog.AppendText(Environment.NewLine + "Blabla")));
});
_bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler((o, args) =>
{
//End long stuff here
this.Dispatcher.Invoke((Action)(() => this.button1.IsEnabled = true));
});
}
private void button1_Click(object sender, RoutedEventArgs e)
{
this.button1.IsEnabled = false;
_bw.RunWorkerAsync();
}
Note that anything that modifies your UI from another thread must be done within a Dispatcher.Invoke or Dispatcher.BeginInvoke call, WPF does not allow you to get or set DependencyProperty values from any thread but the one where the object was created (more about this here).
If you wanted to read from txtLog instead of modifying it, the code would be the same:
//Long stuff here
this.Dispatcher.Invoke((Action)(() =>
{
string myLogText = txtLog.Text;
myLogText = myLogText + Environment.NewLine + "Blabla";
txtLog.Text = myLogText;
}));
That operation is being performed on the UI thread. This means that it will block the Windows message pump from processing until it has completed. no pump = no UI updates. You should launch the job on another thread. I don't know WPF, but in C# I would use either the Thread or BackgroundWorker classes.
do it async. create a backgroundworker process to handle the data and the application will continue to respond. MSDN Resources on the Class. Since WPF is using C# (or VB.net) you can still use the same types of threading objects. I've used the background worker successfully in a WPF app myself.

Categories