Tree view is not updating in background - c#

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

Related

Call Method From BackgroundWorker

I hate that my first question seems to have been answered many times, but I'm still having a tough time getting my head around how to call a method using BackgroundWorker.
I'm processing a very large text file using a series of classes and methods. The entire process is kicked off after the user selects a tool strip item. Sequentially, it goes like this:
User selects the tool strip item
User selects a file to be processed via a dialog box
The action starts
I think I can wrap everything into BackgroundWorker from the moment the user pops the initial dialog box, but what I'd like to do for now is just put the method where all the heavy lifting is done into its own instance of BackGroundWorker. I'll add a ProgressBar, too, but I think I can handle that if I can just get the BackgroundWorker process rolling.
From the top (pseudocode used for example purposes. Much omitted for brevity):
private void ToolStripMenuItem_Click(object sender, EventArgs e)
{
string fileName = openSingleFile.FileName;
processFile(fileName);
}
static public void processFile(string fileName)
{
// many vars/loops exist but not shown
foreach (data in bigData)
{
processItem(stringA, stringB); // <-- this method is where the expensive work is done
x++;
}
}
I've created an instance of BackgroundWorker...:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// Things go here
}
...and I've tried too many things to list, so I've gone back to the beginning for the presentation above.
If I'm understanding BackgroundWorker, I'll need to do the following:
Replace processItem(stringA, stringB) in the above code with something like:
backgroundWorker1.RunWorkerAsync(processItem(stringA, stringB));
...and then do some type of DoWork call?
...and then do some type of RunWorkerCompleted call?
Not sure why my brain is freezing, but I'm embarrassed at the amount of time I've spent on this with no result. Any help would be greatly appreciated. Without StackOverflow, I would have been DOA a long time ago.
FYI: I've referenced other SO posts, MSDN, and DotNetPerls examples. I'm just missing something conceptually, I suppose.
Replace processItem(stringA, stringB) in the above code with something like...
No, that's how you got in trouble. You most definitely want to move the processFile() call to the worker. There is no perceivable benefit from running processItem() in a worker, at least not in the snippet you posted. And doing so is difficult, it would require starting more than one worker. One for each item. Having a lot of workers that each do little work is not very healthy. If it is really necessary then you don't want to use BackgroundWorker, you'll want an entirely different approach with several Threads that consume packets of work from a thread-safe queue. Don't go there if you can avoid it.
The only non-trivial problem to solve is passing the string that processFile() needs. Luckily BackgroundWorker.RunWorkerAsync() has an overload that takes a single object. Pass your string. Obtain its value in your DoWork event handler, casting e.Argument back to a string. Thus:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
string path = (string)e.Argument;
processFile(path);
}
private void processToolStripMenuItem_Click(object sender, EventArgs e) {
backgroundWorker1.RunWorkerAsync(openSingleFile.FileName);
processToolStripMenuItem.Enabled = false;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
processToolStripMenuItem.Enabled = true;
}
Starting up a new background worker is an expensive operation. You don't want to be starting one for each iteration of a loop. Instead, put the entire loop inside of a single background worker's scope.
When ToolStripMenuItem_Click is run create your background worker, have processFile be what is done in the DoWork event handler.
Make sure that when doing that work you're really just doing that work, not updating the UI. You'll want to separate business logic from the user interface. If you want to update the UI with some current progress then call ReportProgress and ensure that there is an event handler to properly update the UI.
If you need to update the UI when the work is all done then you can do so in the RunWorkerCompleted event handler. If the work you are doing generates some result that is used to update the UI use the Result property of the background worker to pass it from the DoWork method to the completed handler.
BackgroundWorker bgw;
In the Load event or constructor:
bgw = new BackgroundWorker();
bgw.WorkerReportsProgress = true;
//bgw.WorkerSupportsCancellation = true;
bgw.DoWork += bgw_DoWork;
bgw.ProgressChanged += bgw_ProgressChanged;
bgw.RunWorkerCompleted += bgw_RunWorkerCompleted;
/
private void ToolStripMenuItem_Click(object sender, EventArgs e)
{
string fileName = openSingleFile.FileName;
bgw.RunWorkerAsync(fileName);
}
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
string fileName = (string)e.Argument;
processFile(fileName);
}
private void bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
int Progress = e.ProgressPercentage;
//Update progressbar here
}
private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//Job completed
}

Main thread not responding when using backgroundworker in c#

i have a program that uses several background workers that continuously load and read text files (size can up to 1MB) and update to oracle database.
The problem i am having is whenever the background worker is reading files, the main thread will become not responding which it should not be. Is this the default behaviour when reading files, is there any way to solve it?
Thanks, any help will be very much appreciated.
Update: [brief view of my program code]
private void program_Load(object sender, EventArgs e)
{
bw1.RunWorkerAsync();
bw2.RunWorkerAsync(); //same function as bw1 but different directory
}
private void bw1_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
Thread.Sleep(5000);
bw1.ReportProgress(0);
}
}
private void bw1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
directoryInfo = /* directory url */;
files = directoryInfo .GetFiles();
foreach (FileInfo file in files)
{
/*
read file line by line
load data into database
update file loading status to UI
*/
}
}
All your actual 'work' is being performed in the bw1_ProgressChanged event handler. This code is executing on the UI/Main thread.
The purpose of the BackgroundWorker.ProgressChanged is to allow the UI to update a progress bar or other widget. The actual work (in this case, loading files) should be performed in the bw1_DoWork method.

Backgroundworker, not running the progressbar

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(...)`

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.

Cross-thread operation exception when worker thread adds to BindingList

I have a worker thread that needs to add items to a BindingList. However, the BindingList is databound to a DataGridView. So, when I try to add to the list, I get an InvalidOperationException (Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on.)
Normally for this exception you would do:
if(winformControl.InvokeRequired) {
winformControl.Invoke(MethodDelegate);
}
However, the databinding confuses things, as there is no Winform control in sight. All I have is the following line, which throws the exception:
ClassInstance.MyBindingList.Add(myObject);
If you have a solution specifically for this scenario, great.
If not, how can I get the worker thread to tell my main thread to perform a particular method (with several parameters supplied by the worker thread)? This may be a preferable option, since my worker thread is actually doing a bunch of stuff at the moment (like writing to the database), and I'm not sure if everything is thread-safe. I'm a student, and new to multithreading, and it really is not my forte yet.
One option here is to tell BindingList<T> to use the sync-context, like this - however, this is arguably not the best approach. I wonder if you could expose your data via an event or similar (rather than adding to the list directly) - then have your UI handle the event by sending to the right thread and adding to the UI model.
In your worker class constructor, try this:
private System.Threading.SynchronizationContext mContext = null;
/// <summary>
/// Constructor for MyBackgroundWorkerClass
/// </summary>
public MyBackgroundWorkerClass(System.Threading.SynchronizationContext context)
{
mContext = context;
}
Then, when you need to invoke something on the UI thread:
private void CallOnTheUiThread(object dataToPassToUiThread)
{
// Make sure the code is run on the provided thread context.
// Make the calling thread wait for completion by calling Send, not Post.
mContext.Send(state =>
{
// Change your UI here using dataToPassToUiThread.
// Since this class is not on a form, you probably would
// raise an event with the data.
}
), null);
}
When creating your worker class from a form on the UI thread, this is what you would pass as the synchronization context.
private void Form1_Load(object sender, EventArgs e)
{
var worker = new MyBackgroundWorkerClass(SynchronizationContext.Current);
}
You can fire an event to the main, UI, thread and there have:
if (this.InvokeRequired)
{
this.Invoke(...);
}
so you are testing on the main Window itself.
BackgroundWorkers are easy to implement if you are able to given the requirements.
Define a DoWork method that runs on a background thread such as saves to the database. The RunWorkerCompleted method is called when DoWork finishes. RunWorkerCompleted runs on the UI thread, and you can update the view's list with no problems.
// on the UI thread
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += DoWork;
worker.RunWorkerCompleted += RunWorkerCompleted;
worker.RunWorkerAsync("argument");
Events:
static void DoWork(object sender, DoWorkEventArgs e)
{
e.Result = "4";
}
static void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error == null)
{
string a = (string)e.Result;
Console.WriteLine(a);
}
else
{
Console.WriteLine(e.Error.Message);
}
}

Categories