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)
Related
Forgive my semantics and programming ignorance.
I'm wanting to run a method, then wait for it to complete before continuing. The initial method makes calls to 2 others and I can't figure out how/where to put 'continue to the next step ONLY AFTER you've completely finished'.
When searching around I came across various ways for Synchronous and Asynchronous methods.
I've tried using Task t = Task.Run(() => startDownload(BTurl, BTlocation)) along with Task.WaitAll() or t.wait() with no luck.
Attempted await but the methods aren't async. (althought startDownloading() has a call to AsyncCompletedEventHandler() which leads to a lot of my confusion).
Tried EventWaitHandle with _ready.Set in various locations (see commented locations A, B and C with findings).
Next was adding private readonly ManualResetEvent _resetEvent = new ManualResetEvent(false); under class and _resetEvent.Set() and resetEvent.WaitOne() similar to EventWaitHandle.
I kind of tried thread.Join() in startDownload(). No joy.
Tried Process.Start.......WaitForExit() with startDownload(). Nope
I know I'm getting somewhere and yet nowhere. For some reason I may need to convert(?) startDownload() to an async method. This is where my confusion lies because my search results demonstrated how to 'wait'
from within a single method and the method I'm using makes calls to 2 others.
In the end though, for me this is a for-fun and learning project. Though I may have jump in a little too deep. [blush]
namespace Download_Extract
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
//static EventWaitHandle _ready = new AutoResetEvent(false);
//static EventWaitHandle _go = new AutoResetEvent(false);
// ***Download and extract***
private void Button1_Click(object sender, EventArgs e)
{
string BTurl = "https://the/file/is/located/here/self_extracting_7z_file.exe";
string BTlocation = Path.Combine(Path.GetTempPath(), "self_extracting_7z_file.exe");
startDownload(BTurl, BTlocation);
//_ready.WaitOne(); //wait here until _ready.Set() is retruned
label3.Text = "Extracting Files......."; //_ready.Set() has been received
Process.Start(BTlocation, #"-aoa -y -oc:\dest_folder").WaitForExit();
label3.Text = "Extracting Files Completed";
}
private void startDownload(string BTurl, string BTlocation)
{
Thread thread = new Thread(() => {
WebClient client = new WebClient();
client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
client.DownloadFileCompleted += new AsyncCompletedEventHandler(client_DownloadFileCompleted);
client.DownloadFileAsync(new Uri(BTurl), BTlocation);
});
thread.Start();
//_ready.Set(); // test C *ProgressBar works, but doesn't wait for completed dl before continuing
}
void client_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
this.BeginInvoke((MethodInvoker)delegate {
double bytesIn = double.Parse(e.BytesReceived.ToString());
double totalBytes = double.Parse(e.TotalBytesToReceive.ToString());
double percentage = bytesIn / totalBytes * 100;
label3.Visible = true;
label3.Text = "Downloading.... "; // + e.BytesReceived + " of " + e.TotalBytesToReceive;
label10.Visible = true;
label10.Text = String.Format("{0:0}", percentage) + "%";
progressBar1.Visible = true;
progressBar1.Value = int.Parse(Math.Truncate(percentage).ToString());
});
//_ready.Set(); // test B *ProgressBar works, but doesn't wait for completed dl before continuing
}
void client_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
this.BeginInvoke((MethodInvoker)delegate {
label10.Visible = false;
label3.Text = "Download completed.";
});
//_ready.Set(); // test A *works, but with issues. pBar delayed, no #%.text shown
}
}
}
Desired outcome of Button1_Click:
Download file.
while downloading, show the Progress Bar and % complete...
Extract file after download is complete.
Error I'm getting:
Message=The process cannot access the file because it is being used by another process
(trying to extract while still downloading)
This is a classic use case for async as you seem to be on the road towards.
_ready.WaitOne(); //wait here until _ready.Set() is returned
is your problem, because it blocks the calling thread, which in this case is the UI thread. Thus, the app freezes and no text/progress bars are updated. The classic solution is to use a TaskCompletionSource.
private TaskCompletionSource<bool> _downloadSource;
Your button click becomes:
private async void Button_Click(…)
{
_downloadSource = new TaskCompletionSource<bool>();
// Start download
await _downloadSource.Task
// Extract
}
The async keyword just allows you to use await. Note that async void is bad practice, except with event handlers (lots of good content out there about why).
Finally when the download is complete:
_downloadSource.TrySetResult(true);
This does the same thing as an AutoResetEvent but asynchronously. What C# means by that is that the method invocation blocks, but the thread is allowed to run. The task system will return the method to the original invocation context when the task completes (at least, by default). MSDN has much more information on async/await and Task/Task<T>
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.
I have developed a PowerPoint addin that converts the currently open presentation to a .wmv file.
It all works nicely. When the presentation is converting I can see a progress bar at the bottom of PowerPoint along with a red X which can be pressed to cancel the conversion process.
Is there a way of programmatically calling this cancel button some how?
This is the code I use to call it and wait for it to finish but I would also like to be able to cancel it while it's in progress.
private void frmUpload_Load(object sender, EventArgs e)
{
try
{
progressBarUpload.Value = 0;
string exportName = "video_of_presentation";
string exportPath = #"C:\Windows\Temp\{0}.wmv";
// Export the currently open presentation
Microsoft.Office.Interop.PowerPoint.Application ppApplication = null;
ppApplication = new Microsoft.Office.Interop.PowerPoint.Application();
ppApplication.Activate();
ppApplication.ActivePresentation.SaveAs(String.Format(exportPath, exportName), Microsoft.Office.Interop.PowerPoint.PpSaveAsFileType.ppSaveAsWMV);
lblUploadStatus.Text = "Status: Converting …";
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
/* run your code here */
do
{
System.Threading.Thread.Sleep(500);
}
while (ppApplication.ActivePresentation.CreateVideoStatus != Microsoft.Office.Interop.PowerPoint.PpMediaTaskStatus.ppMediaTaskStatusDone);
backgroundWorker.RunWorkerAsync();
SetControlPropertyValue(lblUploadStatus, "text", "Status: Uploading …");
}).Start();
}
catch
{
lblUploadStatus.Text = "Status: Error Converting File.";
}
}
Any help would be much appreciated.
Trev
According to MSFT this is not possible :(
Thread is here:
http://social.msdn.microsoft.com/Forums/vstudio/en-US/9308ca45-ec8e-42df-8a5f-a150468a6213/cancel-microsoftofficeinteroppowerpointppsaveasfiletype
Trev
Im trying to make a simple downloader, but i cant make it to update the GUI elements in my Form (Form1).
In the constructor for Form1 i call thread:
Thread t = new Thread(new ThreadStart(fetch));
t.Start();
And fetch looks something like this:
private void fetch()
{
try
{
WebClient webClient = new WebClient();
webClient.DownloadProgressChanged += new DownloadProgressChangedEventHandler(webClient_DownloadProgressChanged);
webClient.DownloadFileCompleted += new AsyncCompletedEventHandler(webClient_DownloadFileCompleted);
webClient.DownloadFile(updateurl, #"foo.iso");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message + "\n\n" + ex.InnerException.ToString());
}
}
None of the events are triggered... altho, by looking at the foo.iso in the folder i see the file increase in size.
One of the events looks like this:
void webClient_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
progressBar.Value = (int)(e.BytesReceived * 100 / e.TotalBytesToReceive);
statusLabel.Text = "Downloading";
descLabel.Text = progressBar.Value + " % complete";
}
You don't need to perform your fetch method in another thread, just use DownloadFileAsync instead of DownloadFile and it should work asynchronously.
Also, that's the reason why DownloadProgressChanged event doesn't trigger:
it works only with asynchronous calls (link).
I don't know, why they are not raised, but the problem is, that you are trying to update the UI from a non-UI thread.
How to do it, can be seen here: How to update the GUI from another thread in C#?
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.