I implemented 'CheckFileSize()' function to BackgroundWorker and a progress bar because the search task can take long. Since adding the BackgroundWorker thread, application times seems to be much slower.. how can I optimize, or what am I doing wrong?
private void UploadApp()
{
label4.Visible = true;
label4.Text = ("Please wait...");
// Assign BackgroundWorker1 to start check file size.
backgroundWorker1.RunWorkerAsync();
}
// Check for invalid file size in 'AppID' folder.
private void CheckFileSize()
{
// Set application directory.
string AppDirectory = ("Z:/Projects/" + AppID);
// Set maximum file size in byte size (2GB).
long fileSizeLimit = 2000000000;
// Get IEnumerable (as in a list) on all files by recursively scanning directory.
var fileList = Directory.EnumerateFiles(AppDirectory, "*", SearchOption.AllDirectories);
// Retrieve the size of files.
long fileSize = (from file in fileList let fileInfo = new FileInfo(file) select fileInfo.Length).Sum();
// Exit application utility if maximum file size found.
if (fileSize >= fileSizeLimit)
{
//MessageBox.Show("Project folder '" + AppID + "' contain file size greater than or equal 2GB. Manual SCM upload required.");
DialogResult MsgResult;
MsgResult = MessageBox.Show("Project folder '" + AppID + "' contain file size greater than or equal 2GB. Manual SCM upload required.",
"Invalid File Size",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
Environment.Exit(0);
}
}
// BackgroundWorker1 runs 'DoWork' in the background to check invalid file size.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// Set for loop to increment progress bar.
for (int i = 0; i <= 100; i++)
{
CheckFileSize();
// Method to report the percentage complete.
backgroundWorker1.ReportProgress(i);
// Cancel BackgroundWorker1.
if (backgroundWorker1.CancellationPending)
{
break;
}
}
}
// Update the progress bar control when the worker thread reports progress.
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
// Enable START and EXIT button when work is done or thread is cancelled.
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
button1.Enabled = true;
button2.Enabled = false;
button3.Enabled = true;
}
}
You are calling CheckFileSize in a loop and that is quite expensive. You would be better off using a FileSystemWatcher that would inform you of any change to files. You could then keep track of each file size change and update the total accordingly.
I believe this is not how reporting progress from a BackgroundWorker works. What you are doing is:
Run the CheckFileSize method 100 times
After each run, increase progress by 1%.
As far as I can tell, all 100 runs of CheckFileSize do the same thing. The only way that result can change is if the contents of the directories change between runs. So, the question is - do you expect the files to change while the code is running?
If not, then you should only run CheckFileSize once. If you need to report progress percentage, you must do this inside the method:
var fileList = Directory.GetFiles(AppDirectory, "*", SearchOption.AllDirectories);
int fileCounter = 0;
long fileSize = 0;
foreach (string file in fileList)
{
fileSize += new FileInfo(file).Length;
if (fileSize >= fileSizeLimit)
{
//display message box etc.
break;
}
fileCounter++;
backgroundWorker1.ReportProgress(((double)fileCounter/fileList.Count)*100);
}
This also has another optimization - the loop ends when the limit is reached, so we don't have to look at the sizes of the rest of the files (your code always goes through all of them).
If the directory content can change, then I don't really see how you can display a progress bar and I would probably go with FileSystemWatcher as Tarik suggests.
Aside from that, I don't really like the usage of Environment.Exit. I believe it would be better to just stop the BackgroundWorker, set a result and deal with it in the RunWorkerCompleted handler.
Related
On my application, when you click the save button, it should download a zip file whilst showing progress and when the download is completed, it should extract the zip file (using DotNetZip lib) with progress.
When I test it, the progress bar shows changes for few seconds, then it freezes for few seconds and goes to last stage of the process (Extracting completed).
This is my code so far:
// Handle nginx save action
private void saveNginX_Click(object sender, EventArgs e)
{
try
{
// Disable save button
(sender as Button).Enabled = false;
// Generate download & save file name
var downloadLink = new Uri(myNginXConfig.selected_resource.DownloadLink);
var saveFilename = #"apps\" + Path.GetFileName(downloadLink.AbsolutePath);
// Check if file already exists
if (File.Exists(saveFilename))
{
// Download completed, proceed to extracting
NginXDownloadCompleted();
}
else
{
// Update status
nginxStatus.Text = "Downloading " + selectNginX.SelectedItem.ToString() + " ...";
Thread.Sleep(1000);
// Init download
using (WebClient webClient = new WebClient())
{
webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(NginXDownloadProgressChangedEvent);
webClient.DownloadFileCompleted += new AsyncCompletedEventHandler(NginXDownloadCompletedEvent);
webClient.DownloadFileAsync(downloadLink, saveFilename);
}
}
}
catch (Exception ex)
{
// Error
Utils.ShowError("Failed to save - " + ex.Message);
// Enable save button
(sender as Button).Enabled = true;
}
}
// Handle nginx download progress changed event
private void NginXDownloadProgressChangedEvent(object sender, DownloadProgressChangedEventArgs e)
{
// Update the progressbar percentage only when the value is not the same.
nginxProgress.Value = e.ProgressPercentage;
// Calculate download progresss
var downloadProgress = string.Format("{0} MB / {1} MB",
(e.BytesReceived / 1024d / 1024d).ToString("0.00"),
(e.TotalBytesToReceive / 1024d / 1024d).ToString("0.00"));
// Update download progress
nginxStatus.Text = "Downloading " + downloadProgress + " ...";
}
// Handle nginx download complete event
private void NginXDownloadCompletedEvent(object sender, AsyncCompletedEventArgs e)
{
if (e.Cancelled != true)
NginXDownloadCompleted();
}
// Handle nginx download complete
private void NginXDownloadCompleted()
{
// Update status
nginxStatus.Text = "Download completed; extracting ...";
nginxStatus.Update();
Thread.Sleep(1000);
nginxProgress.Value = 0;
// Generate download & save file name
var downloadLink = new Uri(myNginXConfig.selected_resource.DownloadLink);
var saveFilename = #"apps\" + Path.GetFileName(downloadLink.AbsolutePath);
// Begin extracting the downloaded zip file
using (ZipFile zip = ZipFile.Read(saveFilename))
{
zip.ExtractProgress += new EventHandler<ExtractProgressEventArgs>(NginXExtractProgressEvent);
zip.ExtractAll("apps", ExtractExistingFileAction.OverwriteSilently);
}
// Json encode setting and save it
// File.WriteAllText(#"apps\NginXConfig.json", JsonConvert.SerializeObject(myNginXConfig, Formatting.Indented));
}
// Handle nginx extract progress event
private void NginXExtractProgressEvent(object sender, ExtractProgressEventArgs e)
{
if (e.TotalBytesToTransfer > 0)
nginxProgress.Value = Convert.ToInt32(100 * e.BytesTransferred / e.TotalBytesToTransfer);
if (e.EventType == ZipProgressEventType.Extracting_AfterExtractAll)
NginXExtractCompleted();
}
// Handle nginx extract completed event
private void NginXExtractCompleted()
{
// Update status
nginxStatus.Text = "Extracting completed; generating config ...";
nginxStatus.Update();
Thread.Sleep(1000);
nginxProgress.Value = 0;
}
the status label text also appears to be doing the same thing. It feels/looks as if the process is not synchronised, it just jumps.
I've tried using Label.Update() method, but that doesn't help.
Any ideas what I might be doing wrong?
I want to procedurally report the progress with status updates on the UI as the events are occurring.
The problem lies in the NginXDownloadCompleted method. While DownloadFileAsync is asynchronous method, ExtractAll is not, so your code asynchronously download file from internet but then freeze main thread for the time of file extracting.
What you need - is to create separate background thread (using any available options: via Thread class, via ThreadPool, via Tasks) and do entire work for both downloading and extracting in this thread in synchronous way.
Some notes:
You do not need Thread.Sleep in main thread at all it won't help
Do not forget to replace asynchronous download in new separate thread to synchronous (DownloadFile instead of DownloadFileAsync)
Progress events that will trigger in background thread will work in different context, so you cannot directly update UI, instead you need to provoke main thread to do this (easiest way is to use Control.BeginInvoke)
I made an application that passes trough an XML file and "extracts" the entries that have a specific value of a certain attribute. The application works fine, but the XML file has over 52,000 entries and only 3 values to sort by (so naturally it takes some time to execute it). Because of that I want to implement a progress bar so I can follow the progress my application made. I googled it, but none of the solutions work for me (the application does it's work, but the progress bar doesn't move at all), so if you could help me that would be grate.
The code that extracts the entries is this:
XmlNodeList nodes;
string ExtractedXML = "";
private void extractingXMLToolStripMenuItem_Click(object sender, EventArgs e)
{
if (nodes.Count != 0)
{
foreach (XmlNode node in nodes)
{
if (String.Compare(node.ChildNodes[6].ChildNodes[0].Attributes.GetNamedItem("code").Value.ToString(), "CodeValue") == 0)
{
tempXML = node.InnerXml;
temp = "<" + node.Name + " code=\"" + node.Attributes.GetNamedItem("code").Value + "\">" + Environment.NewLine;
temp += tempXML + Environment.NewLine;
temp += "</" + node.Name + ">";
ExtractedXML += temp + Environment.NewLine;
temp = "";
}
}
textBox1.Text = ExtractedXML;
}
}
There is a ToolStripMenue item for each value with similar code, only the "CodeValue" changes. How can I implement the progress bar to show me how far did the application get?
Best thing to do, is to not process the XML on the UI thread.
The reason why you don't see the progressbar increasing, is because processing that XML probably uses all resources and the UI thread has no chance of updating it's controls, until processing the XML has finished.
You can use a BackgroundWorker to do the processing, and use the ProgressChanged event to let the UI know that it should update the progressbar.
By doing so, your application will remain responsive, and the UI will not freeze. The UI thread will be handed some CPU time as well, and it is able to update his controls and respond to events.
You should do your long running process inside of a separate thread, BackgroundWorker would be useful here. When kicking off your process call:
bgWorker.RunWorkerAsync(//pass in object to process)
Then have a ReportProgress event where you increment your progress bar e.g.
private void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Value = e.ProgressPercentage;
}
And finally, inside of the do work event you'll want to call report progress with your formula for getting the percentage completed, as well as passing in a processed result, here's an example i've used:
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
bgWorker.ReportProgress((outputGrid.Rows.Count * 100) / importGrid.Rows.Count, result);
}
In almost all tutorials of BackgroundWorker the reportProgress event is handled like this (this example is from MSDN http://msdn.microsoft.com/en-us/library/cc221403(VS.95).aspx)
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; (i <= 10); i++)
{
if ((worker.CancellationPending == true))
{
e.Cancel = true;
break;
}
else
{
// Perform a time consuming operation and report progress.
// _results.Load() downloads XML and save the data to database
System.Threading.Thread.Sleep(500);
worker.ReportProgress((i * 10));
}
}
}
My function downloads XML and save it to database after parsing. I called this function below "// Perform a time consuming operation and report progress." But won't my function run 10 times?
Later i modified Load() adding to variables CountTotal (total number of results) and CountLoaded (number of results saved, it changes as the function progress).
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; (i <= 10); i++)
{
if ((worker.CancellationPending == true))
{
e.Cancel = true;
break;
}
else
{
// Perform a time consuming operation and report progress.
_results.Load() downloads XML and save the data to database
worker.ReportProgress((_results.CountLoaded * 10)); //i did something like this
}
}
}
The problem is that worker.ReportProgress executes after the completion of _results.Load(). How to solve this problem? Are the given examples on internet really bad because they are suggesting to call the function in a loop, or I got them wrong?
Yes - that will execute Load 10 times. The intent in that example is to illustrate usage when you can estimate the overall workload, or report meaningful progress. They are just trying to simulate "some work" with progress indication.
If you can't do that, then just run it async (via BackgroundWorker) but show a scrolling infinite marquee or similar. Don't do it 10 times ;p Alternatively, run Load, then report progress when you process the data. Assuming that takes some time. If all the time is in Load, then...
I'm very much new to programming and have been doing fairly well so far. But progress bars still confuse me. The web unfortunately has to many differing examples for me to get anything out of them. Some say to use background worker some don't, etc. Below I have what I THINK should work but doesn't. Its just supposed to she a progress bar when copying file from one folder to another. Any help is appreciated.
Thanks.
private void btnCopyFiles_Click(object sender, EventArgs e)
{
string folder1 = #"c:\folder1\";
string folder2 = #"c:\folder2\";
DirectoryInfo di = new DirectoryInfo(folder1);
FileInfo[] filelist = di.GetFiles("*.*");
int count = di.GetFiles("*.*").Length;
this.progressBar1 = new System.Windows.Forms.ProgressBar();
progressBar1.Maximum = count;
progressBar1.Minimum = 0;
progressBar1.Step = 1;
foreach (FileInfo file in filelist)
{
try
{
this.Cursor = Cursors.WaitCursor;
File.Copy(folder1 + #"\" + file.Name, folder2 + #"\" + file.Name, true);
progressBar1.PerformStep();
this.Cursor = Cursors.Default;
}
catch (Exception error)
{
MessageBox.Show("Error: " + error);
}
}
}
I'm assuming that you are using Visual Studio and have added the ProgressBar control by dragging it to the form. If this is correct, then following line, may be the problem:
this.progressBar1 = new System.Windows.Forms.ProgressBar();
By recreating the control, you are loosing its link to the form. Just remove or comment that line and try it again.
Overall the issue is that your code continues to execute not allowing the form to re-draw itself as it needs to. Your best, and fastest route is to use a BackgroundWorker to actually perform your operation, using its events to update the progress. Doing this, the update to the progress bar is done on the UI thread, and the UI updates, your file operations go on behind the scenes.
First, create a struct to hold arguments to the BackgroundWorker, which will be passed in DoWorkEventArgs.
public struct CopyStruct
{
public string sourceDir;
public string destDir;
}
Then, do something like this:
private void btnCopyFiles_Click(object sender, EventArgs e)
{
InitializeBackgroundWorker();
CopyStruct copyStruct = new CopyStruct
{
sourceDir = #"C:\folder1\",
destDir = #"C:\folder2\"
};
backgroundWorker.RunWorkerAsync(copyStruct);
}
private void InitializeBackgroundWorker()
{
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.DoWork += backgroundWorker_DoWork;
backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
backgroundWorker.ProgressChanged += backgroundWorker_ProgressChanged;
}
private void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Value = e.ProgressPercentage;
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// do something when finished
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = (BackgroundWorker) sender;
CopyStruct copyStruct = (CopyStruct) e.Argument;
DirectoryInfo di = new DirectoryInfo(copyStruct.sourceDir);
FileInfo[] filelist = di.GetFiles("*.*");
int numFiles = filelist.Length;
for (int i = 0; i < numFiles; i++)
{
FileInfo file = filelist[i];
File.Copy(Path.Combine(copyStruct.sourceDir, file.Name), Path.Combine(copyStruct.destDir, file.Name), true);
// This line updates the progress bar
worker.ReportProgress((int) ((float) i/numFiles*100));
}
}
This contains almost no error checking, so you'll have to add that, but it works with a few test directories on my system.
For the progress bar to be updated in your user interface, the process that is working on something must execute on another thread (otherwise it blocks the UI thread, and the UI will not be updated). The BackgroundWorker is a good candidate for doing this.
Perform your file copy loop in the DoWork event of the background worker, and call the BackgroundWorker.ReportProgress method to, well, report progress. In the event handler for the ProgressChanged event you can set the value in your ProgressBar control. You start the process by calling the RunWorkerAsync method on the BackgroundWorker component.
Since you are new to programming start with this.
Add
Application.DoEvents();
after
progressBar1.PerformStep();
This should get your application working for now. You'll eventually want to move your copy process to a thread/Background worker. But not knowing your abilities, the Application.DoEvents() is probably the easiest fix, but not the prefered fix.
have you tried progressbar1.Increment(x) where x is the number of files transferred?
i want to use a background thread for the process of loading the XML data, possibly with a progress bar to let the user know that the application is actively doing something.
i have written this code through searching the net.
i want to load a XML tree in treeview on winform when a user cliks a Browse button.
In case of a large XML file the winform freezes.So to let the user know that in background the work is going on i want to add a progress bar.i have used a background worker here.
But it is raising an exception of System.ArgumentException showing this message "The URL cannot be empty.\r\nParameter name: url" on xmlDocument.Load(txtFileName.Text); this line.
My xml file is in correct format and is at the proper location where i selected.
But i am unable to find the cause of this exception.
Can you please help out or tell me the correction in my code?
Thanks....
private void btnBrowse_Click(object sender,EventArgs e)
{
bgWorker1.RunWorkerAsync();
StripProgressBar.Value = 0;
toolStripStatusLabel1.Text = "Browsing for a Xml file";
if (open.ShowDialog(this) == DialogResult.OK)
{
txtFileName.Text = open.FileName;
initiatingTree(open.FileName); //this variable gives the name of selected file
}
while (this.bgWorker1.IsBusy)
{
StripProgressBar.Increment(1);
// Keep UI messages moving, so the form remains
// responsive during the asynchronous operation.
Application.DoEvents();
}
}//Browse button
private void bgWorker1_DoWork(object sender, DoWorkEventArgs e)
{
xmlDocument = new XmlDocument();
Thread.Sleep(5000);
xmlDocument.Load(txtFileName.Text);
btnBrowse.Enabled = false;
}
private void bgworker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Set progress bar to 100% in case it's not already there.
StripProgressBar.Value = 100;
if (e.Error == null)
{
MessageBox.Show(xmlDocument.InnerXml, "Download Complete");
}
else
{
MessageBox.Show("Failed to download file");
}
// Enable the Browse button and reset the progress bar.
this.btnBrowse.Enabled = true;
StripProgressBar.Value = 0;
toolStripStatusLabel1.Text = "work finished processing request.";
}//workerCompleted
You're starting the asynchronous process immediately when the user clicks "Browse", by calling
bgWorker1.RunWorkerAsync();
This calls the DoWork method of your background worker, which sleeps for 5 seconds, and pulls the value from txtFileName.Text whether or not the user has completed their entry in the FileOpenDialog.
You'd be better off moving the byWorker1.RunWorkerAsync() (and the busy waiting) into the if (open.ShowDialog(this) == DialogResult.OK) block.
private void btnBrowse_Click(object sender,EventArgs e)
{
StripProgressBar.Value = 0;
toolStripStatusLabel1.Text = "Browsing for a Xml file";
if (open.ShowDialog(this) == DialogResult.OK)
{
txtFileName.Text = open.FileName;
initiatingTree(open.FileName);
bgWorker1.RunWorkerAsync();
while (this.bgWorker1.IsBusy)
{
StripProgressBar.Increment(1);
// Keep UI messages moving, so the form remains
// responsive during the asynchronous operation.
Application.DoEvents();
}
}
}
For these kinds of problems, it can be helpful to put a breakpoint right where the file is going to get loaded, and see what the value is when that happens... you might notice that it's getting called with an empty string.
You might also consider the version of RunWorkerAsync that takes a parameter; you could pass the file in that way, instead of trying to read it asynchronously from the textbox.
And personally, I wouldn't use a loop that calls Application.DoEvents(); instead I'd return control back to the UI thread and then Invoke() onto it from the asynchronous thread to effect the progressbar updates.
When the method bgWorker1.RunWorkerAsync(); is called the event DoWork is fired.
Because the method is called in the beginning of the application, the file name text box is empty.
I hope you've understood.