I'm trying to run a method that gets file list from specified folders and represents it in DataGridView. The method is ran in BackgroundWorker, so I'm expecting GUI to stay active. But it still freezes. Here's an example:
private void startScan_Click(object sender, EventArgs e)
{
bckgrFileScanner.RunWorkerAsync();
}
private void bckgrFileScanner_DoWork(object sender, DoWorkEventArgs e)
{
//for each folder in list perform this function, which scans folder and gets all files
for (int i = 0; i < folderList.Items.Count; i++)
{
GetFileList(ref scannedFiles, folderList.Items[i].ToString(), bckgrFileScanner);
}
}
public static void GetFileList(ref List<FileInfo> fList, string fPath, BackgroundWorker scanner)
{
DirectoryInfo di = new DirectoryInfo(fPath);
FileInfo[] fi = di.GetFiles();
foreach (FileInfo fiTemp in fi)
{
if (fiTemp.Name.StartsWith("~$") == false)
{
//adds items to list of all scanned files
fList.Add(fiTemp);
//reports file name to ProgressChanged method
scanner.ReportProgress(0, fiTemp);
}
}
DirectoryInfo[] dFolders = di.GetDirectories();
//use recursion for all subfolders
foreach (DirectoryInfo d in dFolders)
{
GetFileList(ref fList, d.FullName, scanner);
}
}
private void bckgrFileScanner_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//userstate is filename, so add it to table
filesDataGrid.Rows.Add(e.UserState.ToString());
}
Do not update your reportprogress event at each row of your loop.
Instead, call the ReportProgress every 2 or more iteration (the best way is to compute et step to not update at each row if you have 200000)
And fill the grid when the whole process has finished (in the completed event), to update all the rows. I think progress is meant to update a progress only, not fill a bunch of data into a control but i may be wrong :
from here :
Tips You probably know more than you think about the BackgroundWorker
class. BackgroundWorker has a name that might indicate it is more
complex than it really is. There are many more details about threading
and abort calls, but once you understand that BackgroundWorker is just
a structural "overlay" to threads in Windows Forms, it is quite
intuitive. Here are the steps again:
First, call RunWorkerAsync with an argument. You can pass any argument
to this method on BackgroundWorker, including null. It simply must
inherit from object, which everything does.
Second, custom processing is run. Your expensive code is executed in
the DoWork method. Insert pause here as your program does its
calculations.
Third, it finishes. When your processing is done, RunWorkerCompleted
is called. In this method, you receive the result. In this way, your
BackgroundWorker object modifies an object on another thread, and you
receive it when it is done.
I think it hangs up when too much updates in a short time are required, and even calling Apllication.DoEvents() does not work all the times.
I hope it will help, a little late i know, but it's better than never ;)
This (simplified) example works for me and has a lovely responsive UI:
BackgroundWorker m_objWorker = new BackgroundWorker();
public FormBackgroundWorkerExample()
{
InitializeComponent();
m_objWorker.WorkerReportsProgress = true;
m_objWorker.DoWork += new DoWorkEventHandler(m_objWorker_DoWork);
m_objWorker.ProgressChanged += new ProgressChangedEventHandler(m_objWorker_ProgressChanged);
}
private void btnStart_Click(object sender, EventArgs e)
{
m_objWorker.RunWorkerAsync();
}
private void m_objWorker_DoWork(object sender, DoWorkEventArgs e)
{
//for each folder in list perform this function, which scans folder and gets all files
for (int i = 0; i <= 100; i++)
{
m_objWorker.ReportProgress(i, "FooBar");
Thread.Sleep(1000);
}
}
private void m_objWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
dataGridView1.Rows.Add(e.UserState.ToString());
}
Perhaps this'll give you an idea where yours is going wrong?
Edit: You might want to try it without the datagrid just to try and isolate the problem.
Might be because you are locking the backgroundworker itself.
In your DoWork method
GetFileList(ref scannedFiles, folderList.Items[i].ToString(), bckgrFileScanner);
Normaly you should have access to bckgrFileScanner from your DoWork Method, and just call it directly as bckgrFileScanner.ReportProgress( .... .... )
When passing it like you do, it will now report progress on the background workers thread, which is now not the same as the UI thread. (who owns bckgrFileScanner)
Edit To clarify:
Your UI thread owns bckgrFileScanner and fires RunWorkerAsync()
The stuff that happens in DoWork is now on its own thread.
The DoWork thread is "stealing" the variable bckgrFileScanner
Now the ReportProgress fires on the DoWork thread, instead of the UIs thread
You are calling ReportProgress on scanner. Shouldn't be that bckgrFileScanner?
EDIT
Is by any chance the scannedFiles list databound to the UI? If so a change to the list causes a UI update.
Possible reasons:
I think you just want to report the filename:
//reports file name to ProgressChanged method
scanner.ReportProgress(0, fiTemp.Name);
Or the folderList in DoWork is a UI control:
for (int i = 0; i < folderList.Items.Count; i++)
{
GetFileList(ref scannedFiles, folderList.Items[i].ToString(), bckgrFileScanner);
}
Why not pass the list of folders to the RunWorkerAsync method.
Related
I have the following constellation:
MainForm.cs -> Including all my Form Elements
Program.cs -> includes the main part, which is a xmlreader/writer to alter xml attributes in xml files that can be as large as 4gb
So this little app works but of course the UI gets unresponsive and freezes which I want to avoid, I also hope to reduce the duration of this process on the way
I start the call of my xmlread/write method from a BtnClick event:
void BtnApplyChangesClick(object sender, EventArgs e)
{
Program p = Program.Instance;
pbApplyChanges.Minimum = 0;
pbApplyChanges.Step = 1;
Cursor.Current = Cursors.WaitCursor;
foreach(DataGridViewRow cr in dataGridView2.Rows)
{
pbApplyChanges.Maximum = dataGridView2.Rows.Count;
p.changeElements(cr.Cells["Filename"].Value.ToString(), txtTenant.Text, txtDate.Text, txtEvtId2.Text);
pbApplyChanges.PerformStep();
}
Cursor.Current = Cursors.Arrow;
MessageBox.Show("Job done");
}
In the call I use my singleton instance of Program.cs and my main Method there (changeElements) uses 4 String params, that are all taken from information in the Form! (I suppose this is kinda bad practice but it worked so far...)
When I tried to replace this method call with a backgroundWorker (itself made the method call then) I failed as the method call wasn't even made... I found out that UI elements can't be accessed from the BW thread, so I suppose this is also the reason for my method call not working?!
So how can I get this constellation to work? Do I have to pass all 4 string Params AND the class instance (of Program.cs) to the background worker? Is BW even the best tool for the job?
In general the BackgroundWorker shouldn't access any UI-Elements. It's an old advice in Winforms that accessing UI-Elements should just happen from the UI-Thread.
You can use the Background-Worker like this:
private void Main(string[] args)
{
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += Bw_DoWork;
bw.RunWorkerCompleted += Bw_RunWorkerCompleted;
//Parameter you need to work in Background-Thread for example your strings
string[] param = new[] {"Text1", "Text2", "Text3", "Text4"};
//Start work
bw.RunWorkerAsync(param);
}
//Do your Background-Work
private void Bw_DoWork(object sender, DoWorkEventArgs e)
{
string[] param = e.Argument as string[];
//Process your long running task
e.Result = null; //Set your Result of the long running task
}
//Taking your results
private void Bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//Apply your Results to your GUI-Elements
myTextBox1.Text = e.Result.ToString();
}
Background-Worker is some old school stuff by the way, so if you like to learn something new take a look here and inform yourself about the TPL. This gives you a better handling of asynchronous.
In fact I think it's not really good to store 4gb data in a XML-File. Do you think about a Database? Or split the XML-File in many XML-Files? So you would be able to read data in chunks.
I hope this helps you.
I don't use background worker for this. I use normal threads instead. Try this code:
public void ButtonDoWork_Click(eventArgs......) {
DoWorkThread = new Thread(new ThreadStart(DoWork)); // Setup thread
DoWorkThread.isBackground = true; // Its background so, we need to set background flag
DoWorkThread.Start(); // Start the thread
}
private Thread DoWorkThread: // our Thread object
private void DoWork() { // This void contains action that will be performed by thread
//TODO: Background processing. To update UI from another thread use Control.Invoke(...)
}
Please note, I don't tested this code - I write it from my memory and it's late so it can not work.
You can also read about Threads at MSDN :)
Ok, I've been banging my head against this all day. I'm still fairly new to programming, and its quite possible that my entire approach here is misguided. But anyway...so I have a simple gui app with a listbox full of folders, and I'm executing an operation on every file in each folder sequentially. This is a very long operation, so I have two progress bars - one for each file, and one for each folder.
private void buttonApplySelected_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
double percentToIncrement = 100.0 / Convert.ToDouble(selectedDirList.Count);
double percentComplete = percentToIncrement;
folderProgressBar.Value = 0;
foreach (string dir in selectedDirList)
{
engine = new OEngine.OEngine(dir, backgroundWorker1);
engine.ProcessSelected(processType);
int percentCompleteInt = Convert.ToInt32(percentComplete);
folderProgressBar.Value = percentCompleteInt;
percentComplete += percentToIncrement;
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
fileProgressBar.Value = e.ProgressPercentage;
}
The BackgroundWorker itself is passed down to the engine, and it updates its progress within the code of the engine processing that folder. (This is probably my first mistake.) The UI catches that ProgressChanged event and updates the fileProgressBar in it's own thread.
But the folderProgressBar needs to be updated once every pass through the for loop, but it gives me Cross-thread operation not valid: Control 'folderProgressBar' accessed from a thread other than the thread it was created on.
If I move it out of the for loop, it wont update after each folder.
If I move all the UI updating out of the DoWork function, and call the DoWork function in a for loop instead, it obviously doesnt wait for each folder to finish and I get "the worker is still busy" exception.
Any ideas?
The general solution for windows form:
Use
WindowsFormsSynchronizationContext syncContext = new WindowsFormsSynchronizationContext();
...
//in the background work or any non UI Thread
//Trigger an update in the GUI thread; one can also use syncContext.Send (depending on the need for syncrhonous or async operation)
syncContext.Post(UpdateGUI, userData);
...
void UpdateGUI(object userData)
{
//update your progress bar
}
If using wpf, declare a variable syncContex.
SynchronizationContext syncContext;
//And when in the UI thread, set the variable's value as
syncContext = SynchronizationContext.Current;
//Then from the non-UI thread,
syncContext.Post(UpdateGUI, userData);
I am stuck on an issue where I am using Backgroundworker to show the progress of my work in a progress bar. Code used for backgroundworker:-
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(200);
for (int i = 0; i <= 100; i++)
{
Delegate del= new DELEGATE(simulateHeavyWork);
this.Invoke(del);
backgroundWorker1.ReportProgress(i);
if (backgroundWorker1.CancellationPending)
{
e.Cancel = true;
backgroundWorker1.ReportProgress(0);
return;
}
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
percentLabel.Text = e.ProgressPercentage.ToString() + "%";
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
MessageBox.Show("Cancelled");
}
else
{
MessageBox.Show("Completed");
}
}
I have created a delegate on the code:-
public partial class Form1 : Form
{
private delegate void DELEGATE();
public Form1()
{
InitializeComponent();
}
private void simulateHeavyWork()
{
Thread.Sleep(100);
...lines of code to perform some search logs operation..
....
}
}
The functionality I want to achieve is that progress bar should report the progress of my function simulateHeavyWork() which is actually using UI thread as it needs to take input from my form controls and update it as well.
Now the problem which is happening is that code is actually calling simulateHeavyWork() and gives the output that is updating ui controls and work is done. (Note: I have used delegate here to avoid error cross controls running on ui thread as my function needs to use UI controls.)
Once that is done, it starts updating progress bar and which is wrong and looks like it calls simulateHeavyWork again and again with the gap of sleep(100).
user3222101, as Andy stated before, you are running simulateHeavyWork() continuously. Moreover, by calling Invoke you are running this method in the UI thread which cause an extra sleep in the UI thread. Basically Invoke uses the message loop (pump) of the Control you use it on (Form1 in that case) and put your delegate to the queue of the UI thread in order to execute. This is not a good practice I think, due to the Sleep() call and time consuming log operations in your simulateHeavyWork() method.
I hope, understand you problem clearly.What I suggest is separation of the time consuming log operations from UI thread. Do not spend the valuable time of UI thread with slow and boring I/O operations. Get the values from the controls (using Invoke in the BackgroundWorker as I will explain below), do whatever you want in BackgroundWorker and update your GUI (again using Invoke) without touching the UI thread for this kind of heavy tasks.
As Andy suggested, you can pass data via the parameter of RunWorkerAsync and you should create a class which can store any data you need (because it accepts only one parameter). However, you can get the values from your Form whenever you need from another thread by using Invoke. Invoke
method also returns the value from your delegate (please see the example at the link below) and this gives you a chance to get the values of your controls on the form. Create a delegate which returns an object of type class that you crated for RunWorkerAsync and use this values in the BackgroundWorker thread. Please, have a look at the example in here.
public static string GetTextThreadSafe(this TextBox box)
{
return GetTextBoxText(box);
}
Also, example uses Func<...> in order to return value.
By this way you can sleep (in BackgroundWorker thread) for a while then get the values from your controls (current values) and do whatever you want (again in BackgroundWorker thread). I think, this improves your code.
From your question: "which is wrong and looks like it calls simulateHeavyWork again and again with the gap of sleep(100)."
Of course it calls. Just look at your code:
for (int i = 0; i <= 100; i++)
{
Delegate del= new DELEGATE(simulateHeavyWork);
this.Invoke(del);
So you are calling simulateHeavyWork 100 times here. And since you've typed Thread.Sleep(100); in the body of simulateHeavyWork - gap between calls is about Sleep(100)
Well, what I am trying to do is show a animated gif while it reads a directory full of files, however the UI freezes, which is ok, but i would like to keep the gif running till the operation is finished. Any ideas?
I am doing it on a Windows Form using VS2010 C#
Here is some example code how you can Load your files aysnchronous. Maybe it helps you. I like this way more than using DoEvents. With DoEvents I had already have some ungood side-effects, therefore I try not to use it.
BackgroundWorker bgWorker = new BackgroundWorker() { WorkerReportsProgress=true};
bgWorker.DoWork += (s, e) => {
// Load here your file/s
// Use bgWorker.ReportProgress(); to report the current progress
};
bgWorker.ProgressChanged+=(s,e)=>{
// Here you will be informed about progress and here it is save to change/show progress. You can access from here savely a ProgressBars or another control.
};
bgWorker.RunWorkerCompleted += (s, e) => {
// Here you will be informed if the job is done.
// Use this event to unlock your gui
};
bgWorker.RunWorkerAsync();
You have two options.
Start a separate thread to handle the file operations.
periodically call Application.DoEvents() within the file loop. That will cause your app to process pending messages (thus updating your UI), but will negatively impact the speed of the file processing loop.
Posting from my phone so no example links.
Run enumeration is a separate thread and update the GUI in the main thread.
Would something like this work with backgroundWorker?
private void buttonRename_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
foreach (ListViewItem myitem in listView.Items)
{
try
{
//Rename
}
catch
{
}
}
}
To play a bit with threading, delegates and backgroundworkers, I'm putting together a few small applications, I'm having a bit of trouble with one of them.
I've a Windows form, with a textbox, a button and a richttext.
When I press the button, the text in the textbox is used as a paramter to instantiate a class, like this:
public partial class Form1 : Form
{
private BackgroundWorker backgroundWorker;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += new DoWorkEventHandler(worker_DoWork);
backgroundWorker.RunWorkerAsync();
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
new Thread((ThreadStart)delegate()
{
this.BeginInvoke((ThreadStart)delegate()
{
foreach (string line in textBox1.Lines)
{
Dig digger = new Dig(line, textBox1.Text);
digger.DomainChecked += new Dig.DomainCheckedHandler(OnUpdateTicker);
string response = digger.GetAllInfo();
richTextBox1.AppendText(response);
Application.DoEvents();
}
});
}).Start();
}
void OnUpdateTicker(string msg)
{
new Thread((ThreadStart)delegate()
{
this.BeginInvoke((ThreadStart)delegate()
{
label4.Text = msg;
Application.DoEvents();
});
}).Start();
}
}
When debugging I run into a 'textBox1.Lines' threw an exception of type 'Microsoft.VisualStudio.Debugger.Runtime.CrossThreadMessagingException'
Any tips on how to solve this problem?
First, there is no need to create new threads inside DoWork; the whole idea with the BackgroundWorker is that DoWork is executed on a separate thread. Second, since DoWork is executed on a separate thread and UI controls can be modified only on the UI thread, you need to invoke those updates correctly. So, a rewritten version of worker_DoWork could look like this:
void worker_DoWork(object sender, DoWorkEventArgs e)
{
foreach (string line in textBox1.Lines)
{
Dig digger = new Dig(line, textBox1.Text);
digger.DomainChecked += new Dig.DomainCheckedHandler(OnUpdateTicker);
string response = digger.GetAllInfo();
richTextBox1.Invoke((Action) delegate { richTextBox1.AppendText(response); });
}
}
Note how the code does not explicitly spawn any new threads, and also how the AppendText method call is done through a Control.Invoke call, forcing it to execute on the UI thread.
The main reason is that the textbox is not owned by the background thread.
Your UI thread owns all the UI objects, and you're spinning up a background thread when a button is pressed. That background thread should not have access to any UI objects.
If you want the value of the textbox to be used, you'll need to pass it to your background thread another way.
Have a look here for an explanation (and solution).
You can only update controls on the main thread from the main thread itself, unless you explicitly tell your program that it's ok to do, by using the .Invoke method of the control.
From: http://www.albahari.com/threading/part3.aspx
Control.Invoke
In a multi-threaded Windows Forms application, it's illegal to call a method or property on a control from any thread other than the one that created it. All cross-thread calls must be explicitly marshalled to the thread that created the control (usually the main thread), using the Control.Invoke or Control.BeginInvoke method. One cannot rely on automatic marshalling because it takes place too late – only when execution gets well into unmanaged code, by which time plenty of internal .NET code may already have run on the "wrong" thread – code which is not thread-safe.