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#?
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'm new to using event handlers and backgroundworkers, so I may be missing something completely obvious here. Still, I've been trying to fix this for two days, so I thought I might as well see what anyone had to say.
I have a backgroundworker called SqlExpressDownloader. It starts running at the beginning of my program, the rest of the work runs, and then it should wait for the operations in the SqlExpressDownloader_DoWork() method to complete before continuing. The only problem is that for some reason whenever I do while(SqlExpressDownloader.IsBusy), it always responds as busy and therefore will wait forever.
The code for the event handler is here:
private void SqlExpressDownloader_DoWork(object sender, DoWorkEventArgs e)
{
string sSource = string.Format("{0}\\{1}", Paths.Settings_Common, "sqlexpr_x64_enu.exe");
Debug.WriteLine(sSource);
Debug.WriteLine("http://www.elexioamp.com/Install/redistributables/sql2008r2express/sqlexpr_x64_enu.exe");
if (!System.IO.File.Exists(sSource))
{
WebClient oWebClient = new WebClient();
oWebClient.DownloadProgressChanged += DownloadProgressChanged;
oWebClient.DownloadDataCompleted += DownloadComplete;
oWebClient.DownloadFileAsync(new System.Uri("http://www.elexioamp.com/Install/redistributables/sql2008r2express/sqlexpr_x64_enu.exe"), sSource);
while (oWebClient.IsBusy)
{
Thread.Sleep(100);
}
e.Result = "";
DownloadFinished = true;
}
}
I have watched the code and have watched it complete this method. I even added a return after the DownloadFinished = true, but it still responds as busy. What I want to know is how to make the backgroundworker respond as not busy.
EDIT
The events are all added in the constructor as shown here:
SqlExpressDownloader = new BackgroundWorker();
SqlExpressDownloader.DoWork += new DoWorkEventHandler(this.SqlExpressDownloader_DoWork);
SqlExpressDownloader.RunWorkerCompleted += new RunWorkerCompletedEventHandler(this.SqlExpressDownloader_RunWorkerCompleted);
The RunWorkerCompleteEventHandler looks like this:
private void SqlExpressDownloader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
Debug.WriteLine("The actions are complete.");
}
else
{
Debug.WriteLine("Error in completed work.");
}
}
But, when I debugged it last, it didn't actually trigger.
Instead of querying SqlExpressDownloader.IsBusy in a loop, try subscribing to the RunWorkerCompleted event of the BackgroundWorker and place your code in there that should only occur after the DoWork event has completed.
You'll also have access to the RunWorkerCompletedEventArgs, which you can check to make sure no error was thrown from the DoWork portion of your BackgroundWorker.
...
...
SqlExpressDownloader.RunWorkerCompleted += SqlExpressDownloader_RunWorkerCompleted;
SqlExpressDownloader.RunWorkerAsync();
}
private void SqlExpressDownloader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
// do something in response to the error
}
// stuff to do after DoWork has completed
}
I found Joe Albahari's tutorial helpful when I was learning how to use these.
You can replace your code with more elegant async/await solution like this
private async Task SqlExpressDownloadAsync()
{
string sSource = string.Format("{0}\\{1}", Paths.Settings_Common, "sqlexpr_x64_enu.exe");
Debug.WriteLine(sSource);
Debug.WriteLine("http://www.elexioamp.com/Install/redistributables/sql2008r2express/sqlexpr_x64_enu.exe");
if (!System.IO.File.Exists(sSource))
{
WebClient oWebClient = new WebClient();
oWebClient.DownloadProgressChanged += DownloadProgressChanged;
oWebClient.DownloadDataCompleted += DownloadComplete;
await oWebClient.DownloadFileTaskAsync(new System.Uri("http://www.elexioamp.com/Install/redistributables/sql2008r2express/sqlexpr_x64_enu.exe"), sSource);
}
}
I had a similar issue. DownloadASync would fire but .IsBusy would always stay on true.
This probably won't be a common problem, just thought I share my resolution.
I used
MessageBox.Show(new Form() { TopMost = true }, "", "")
This was the cause. I also tried:
var t = new Form() { TopMost = true };
MessageBox.Show(t, "", "");
t.Dispose();
This caused the same issue.
My code had multiple threads, I assume one of them must have gotten stuck, or perhaps the MessageBox(the new Form() { TopMost = true; } ) call created a stuck thread.
As soon as I removed that part, eg.
MessageBox.Show("", "");
Everything worked as expected again.
So maybe you are creating another thread somewhere that is causing your issue.
I have a Winforms application which is working fine.. using a BackgroundWorkerThread to manage GUI usability during processing of serial data to a device.
It's working fine.
Now, I am adding a new method, and copying what I did in other forms. But I am getting a cross thread exception.
I declare my BWT like this:
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerSupportsCancellation = true;
bw.DoWork += DownloadGpsDataFromDevice;
bw.WorkerReportsProgress = true;
bw.RunWorkerAsync();
I then have a method delared like this, which does the background work:
private void DownloadGpsDataFromDevice(object sender, DoWorkEventArgs e)
{
_performScreenUpdate = true;
tsStatus.Text = "Downloading GPS Data...";
Invalidate();
Refresh();
Common.WriteLog("Extracting raw GPS data. Sending LL.");
ReplyString raw = DeviceServices.ExecuteCommand("$LL");
The DeviceServices.ExecuteCommand("$LL"); is the bit that does the work, but I am getting the exception on the previous line, where I log to a text file. Now, that makes you worry - writing to a file. However, I have done this thousands of times in another BWT.
I made the writing thread safe. Here this my Common.WriteLog method:
public static void WriteLog(string input)
{
lock (_lockObject)
{
WriteLogThreadSafe(input);
}
}
private static void WriteLogThreadSafe(string input)
{
Directory.CreateDirectory(LogFilePath);
StreamWriter w = File.AppendText(LogFilePath + #"\" + LogFileName);
try
{
w.WriteLine(string.Format("{0}\t{1}", DateTime.Now, input));
}
catch (Exception e)
{
System.Console.WriteLine("Error writing to log file!");
System.Console.WriteLine("Tried to write: [" + input + "]");
System.Console.WriteLine("Failed with error: [" + e.Message + "]");
}
finally
{
w.Close();
}
}
This have been working for ages. I don't believe the error is there. I think I am just missing something on the call maybe?
You cannot change UI elements from BackgroundWorker thread. You'll have to marshall back to UI thread by calling Invoke().
Try this
private void DownloadGpsDataFromDevice(object sender, DoWorkEventArgs e)
{
_performScreenUpdate = true;
Invoke((MethodInvoker)(() => {
tsStatus.Text = "Downloading GPS Data...";
Invalidate();
Refresh();
});
...
The issue is that you are updating UI elements from non-UI thread:
Those lines should not be inside of DownloadGpsDataFromDevice
tsStatus.Text = "Downloading GPS Data...";
Invalidate();
Refresh();
To take advantage of BackgroundWorker run method bw.ReportProgress(0);. Update UI in ProgressChanged handler, which was specifically designed for this purpose.
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.ProgressPercentage = 0)
{
tsStatus.Text = "Downloading GPS Data...";
Invalidate();
Refresh();
}
}
Some instances can't or should not be accessed by multiple threads. You have two options to protect your data from cross thread exceptions.
You can lock your object when you access it from multiple threads with a lock:
object locker = new object();
SomeObject MyObject = new SomeObject();
private void FromMultipleThread()
{
lock(locker)
{
MyObject = OtherObject;
}
}
Your second option is to lock your thread with a ManualResetEvent. It is very simple, you only have to call WaitOne() from you ManualResetEvent to lock your thread while an other thread access your "cross threaded" Object.
In your case, you would want to change your UI from the reportProgress of your backgroundWorker. The reportProgress will come back to the initial thread, then you can modify your UI.
Are you sure that is the right line? I don't think you should be able to update the ui in your worker. Try commenting out the gui update and clean and build your solution to see if the logging is really the problem. To update the ui, set WorkerReportsProgress and create an event handler for that to update the ui and report progress in the worker.
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 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.