I am using a background worker to update some tables in sqlserver. the progressbar max is getting set to the correct value, the progressbar value is being incremented, the backgroundworker progresschanged is being called correctly with correct value, yet the bar is not progressing.
here is the code for the form
in the background_dowork method there is a loop which calls updateProgressBarValue which works with correct values.
public InterfaceConvertLonLat()
{
InitializeComponent();
Shown += new EventHandler(Form1_Shown);
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
}
public void ConvertLonLat_Load(object sender, EventArgs e)
{
}
public void updateProgressBarValue()
{
progressBar1.Value++;
backgroundWorker1.ReportProgress(progressBar1.Value);
}
public void setProgressBarMax(int max)
{
progressBar1.Maximum = max;
MessageBox.Show("setprogressbarmax " + max);
}
public void Form1_Shown(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
public void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
convert.OSGB36ToWGS84("paf");
}
public void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
here is the loop contained in another class which calls updateprogressbarvalue, this is being fired and as stated backgroundworker1_progressChanged is being fired but the bar is not moving.
con.setProgressBarMax(address.Tables[0].Rows.Count);
foreach (DataRow LonLat in address.Tables[0].Rows)
{
con.updateProgressBarValue();
Double lon = 0;
Double lat = 0;
lat = Convert.ToDouble(LonLat["LTO"]);
lon = Convert.ToDouble(LonLat["LGO"]);
LocalToWGS84(ref lat, ref lon, OGB_M);
cmd1.Parameters["#LTW"].Value = lat;
cmd1.Parameters["#LGW"].Value = lon;
string dbQuery1 = "update " + tableName + " set LTW = #LTW, LGW = #LGW";
cmd1.CommandText = (dbQuery1);
cmd1.CommandType = CommandType.Text;
cmd1.Connection = conn;
cmd1.ExecuteNonQuery();
}
}
catch (Exception e)
{
MessageBox.Show("error converting: " + e.Message);
}
finally
{
conn.Close();
}
When reporting progress you need to fire an event from the background worker to tell the progressbar it needs updating. This can be done using the below:
backgroundWorker1.ReportProgress(10);
Change the value to what you need to demonstrate increased progress in your code. The progress changed event will mostly run on the same thread as your GUI so you should have no cross thread issues. One exception is if your form is being called from Excel via addin in which case Excel will be on the main thread.
You've got a number of issues - Your UpdateprogressBarValue() just increases the value of the progress bar but doesn't keep track of how many times its been called / what the current value is - so if you call it 101 times (assuming a range of 0-100), you'll get an OutOfRangeException
Your DoWork() method doesn't seem to call the update at all (either directly or by raising an event).
You can use events to do this but you're better off using delegates or perhaps anonymous functions. Something like...
public void setProgress(int value) {
progressBar1.invoke(delegate{ progressBar1.Value = value; }
}
then just call setProgress(0) through setProgress(progressBar1.MaxValue) from your DoWork() method
The following is seriously wrong:
public void updateProgressBarValue()
{
progressBar1.Value++; // not thread-safe
backgroundWorker1.ReportProgress(progressBar1.Value);
}
ReportProgress() is intended to be called form DoWork, it should not even read a Control property.
You should maintain a counter in the foreach loop and feed that to the progress mechanism.
Now this does not directly indicate why it doesn't move but you do not have a Completed handler. Are you sure the process finishes at all?
If an exception escapes your DoWork you will never know what happened.
private void Form1_Load(object sender, System.EventArgs e)
{
// Start the BackgroundWorker.
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i <= 100; i++)
{
// Wait 100 milliseconds.
Thread.Sleep(100);
// Report progress.
backgroundWorker1.ReportProgress(i);
}
}
private void backgroundWorker1_ProgressChanged(object sender, progress e)
{
// Change the value of the ProgressBar to the BackgroundWorker progress.
progressBar1.Value = e.ProgressPercentage;
// Set the text.
this.Text = e.ProgressPercentage.ToString();
}
I think this might be caused by the fact that the UI in windows forms is running in its own thread/context. In this case you probably need to use Invoke to make adjustments to the UI.
Something like this:
public void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Invoke(new ProgressDelegate(UpdateProgress), e.ProgressPercentage);
}
delegate void ProgressDelegate(decimal value);
private void UpdateProgress(decimal value)
{
progressBar1.Value = value;
}
Related
I have the main thread which is wizard in WPF.
after user finished set the properties of the wizard, it processing data.
It takes a few seconds and I would like to raise a progress bar which report on the progress.
Hence, I set always on the main thread variable call currentStep.
I have totally thresholdStep steps which equals to 12.
So I want that the progress bar will work as a thread but it will also will be connected to the main thread by using currentStep variable.
So, I used by background worker like this:
public partial class MessageWithProgressBar : Window
{
private BackgroundWorker backgroundWorker = new BackgroundWorker();
public MessageWithProgressBar()
{
InitializeComponent();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.ProgressChanged += ProgressChanged;
backgroundWorker.DoWork += DoWork;
backgroundWorker.RunWorkerCompleted += BackgroundWorker_RunWorkerCompleted;
}
private void DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(100);
int i = (int)e.Argument;
backgroundWorker.ReportProgress((int)Math.Floor((decimal)(8*i)));
if (i > GeneralProperties.General.thresholdStep)
backgroundWorker.ReportProgress(100);
}
private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progress.Value = e.ProgressPercentage;
}
private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
WindowMsg msg = new WindowMsg();
msg.Show();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
if (backgroundWorker.IsBusy == false)
backgroundWorker.RunWorkerAsync(GeneralProperties.General.currentStep);
}
}
In addition, I called the background worker from the main thread as below:
MessageWithProgressBar progress = new MessageWithProgressBar();
progress.Show();
What acutally happens is that DoWork called only once with currentStep = 1 and it don't updates in relation to the main thread which also updated currentStep dependents on it's progress.
Any ideas how to solve it?
Thanks!
Change your DoWork method like below:
private void DoWork(object sender, DoWorkEventArgs e)
{
Thread.Sleep(100);
int i = (int)e.Argument;
do
{
i = GeneralProperties.General.currentStep;
backgroundWorker.ReportProgress((int)Math.Floor((decimal)(8 * i)));
if (i > GeneralProperties.General.thresholdStep)
backgroundWorker.ReportProgress(100);
}
while (i < GeneralProperties.General.thresholdStep);
}
Just make sure your are not getting thread synchronization problem with GeneralProperties.General object, if you are then use lock when accessing the object.
UPDATE:
For update problem:
private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
System.Windows.Application.Current.Dispatcher.Invoke(new Action(() =>
{
progress.Value = e.ProgressPercentage;
}), null);
}
In my XML editor I want to be able to open multiple files at once using an index file. Obviously, depending on the amount of files, this can take a bit of time and I want to use a progress bar to notify the user that the programm is still loading and doing something.
From what I have researched the way to keep the UI progress bar updated is using a BackgroundWorker.
public MainWindow()
{
InitializeComponent();
tabList = new ObservableCollection<FileTab>();
tabControl.ItemsSource = tabList;
backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += backgroundWorker_DoWork;
backgroundWorker.ProgressChanged += backgroundWorker_ProgressChanged;
backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
backgroundWorker.WorkerReportsProgress = true;
}
(...)
private void OpenProjectButtonClick(object sender, RoutedEventArgs e)
{
openingProgressBar.Value = 0;
openingProgressBar.Visibility = System.Windows.Visibility.Visible;
backgroundWorker.RunWorkerAsync();
}
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
int i = 0;
foreach (IndexFile file in indexManager.fileList)
{
this.Dispatcher.Invoke((Action)(() =>
{
tabList.Add(new FileTab(file.filePath));
}));
i++;
Console.WriteLine("-(DoWork)->" + i);
double percentage = (Convert.ToDouble(i) / Convert.ToDouble(indexManager.fileList.Count)) * 100;
Console.WriteLine("-(DoWork.percentage)-> "+ percentage);
backgroundWorker.ReportProgress((int)percentage);
}
}
void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
openingProgressBar.Value = e.ProgressPercentage;
Console.WriteLine("-(ProgressChanged)->" + openingProgressBar.Value);
}
void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
openingProgressBar.Visibility = System.Windows.Visibility.Collapsed;
backgroundWorker.Dispose();
Console.WriteLine("-(RunWorkerComplete)-> Done");
}
Since I'm accessing the tablist in the DoWork-Method I wrap that call in the Dispathcer.Invoke. In this form the code kind of does what I want. It makes the collapsed progressBar visible and updates it every once in a while. Sadly it doesn't update the percentage after every file loaded. From what I can see in the console, the ProgressChanged execution lags behind the DoWork. From my understanding it is called in every iteration of the loop though. And even if it fires the UI doesn't always respond to that.
So my question is: Am I still blocking the UI thread somehow and how could I fix it?
The Problem is that
you are do a coupling between UI and BackgroundWorker
The Solution
return on ReportProgress your FileTab object
void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
int i = 0;
foreach (IndexFile file in indexManager.fileList)
{
i++;
Console.WriteLine("-(DoWork)->" + i);
double percentage = (Convert.ToDouble(i) / Convert.ToDouble(indexManager.fileList.Count)) * 100;
Console.WriteLine("-(DoWork.percentage)-> "+ percentage);
backgroundWorker.ReportProgress((int)percentage,new FileTab(file.filePath));
}
}
then add your FileTab object in your backgroundWorker_ProgressChanged to the tabList
Try to move the reporting progress operation into the dispatcher's operations:
foreach (IndexFile file in indexManager.fileList)
{
this.Dispatcher.Invoke((Action)(() =>
{
tabList.Add(new FileTab(file.filePath));
Console.WriteLine("-(DoWork)->" + i);
double percentage = (Convert.ToDouble(i) / Convert.ToDouble(indexManager.fileList.Count)) * 100;
Console.WriteLine("-(DoWork.percentage)-> "+ percentage);
backgroundWorker.ReportProgress((int)percentage);
}));
i++;
}
It's possible that dispatcher runs in another thread...
I want to create a basic multi-thread application using a progress bar. Meaning that this progress bar will run on a different thread while the main thread is busy in the large process it is doing. I've seen a lot of tutorials about it. But the thing that they are multi-threading is the one that doing the large process. The progress bar in the other form is just showing a simple progress bar that runs and complete using a timer.
This is the code I have now.
For the thread:
public void thread()
{
Form6 for6 = new Form6();
for6.Show();
}
TH1 = new Thread(thread);
TH1.Start();
For the progress bar (Code inside form 6)
private void timer1_Tick(object sender, EventArgs e)
{
progressBar1.Increment(+1);
if (progressBar1.Value == 99)
{
this.Close();
}
}
private void Form6_Load(object sender, EventArgs e)
{
timer1.Start();
}
My problem is the thread in here doesn't run the Form6. Is there any way for me to do this?
Instead of using main thread for large processing you can use the Background worker for all the processing.
Here's a simple example to do it.
public partial class Form1 : Form
{
BackgroundWorker bgw = new BackgroundWorker();
public Form1()
{
InitializeComponent();
label1.Text = "";
label2.Text = "";
}
private void button1_Click_1(object sender, EventArgs e)
{
bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
bgw.ProgressChanged += new ProgressChangedEventHandler(bgw_ProgressChanged);
bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
bgw.WorkerReportsProgress = true;
bgw.RunWorkerAsync();
}
void bgw_DoWork(object sender, DoWorkEventArgs e)
{
int total = 57; //some number (this is your variable to change)!!
for (int i = 0; i <= total; i++) //some number (total)
{
System.Threading.Thread.Sleep(100);
int percents = (i * 100) / total;
bgw.ReportProgress(percents, i);
//2 arguments:
//1. procenteges (from 0 t0 100) - i do a calcumation
//2. some current value!
}
}
void bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
label1.Text = String.Format("Progress: {0} %", e.ProgressPercentage);
label2.Text = String.Format("Total items transfered: {0}", e.UserState);
}
void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//do the code when bgv completes its work
}
}
Instead of the ProgressBar, you should really move your long-running, non-UI code into a separate thread. The standard and easier way of doing this in WinForms is to use BackgroundWorker component, which can raise ProgressChanged event where you can update your UI. Important to note that ProgressChanged event is raised on the UI thread, not on the worker thread, so you don't even need to use Invoke() to perform UI operations (such as updating your ProgressBar).
you must use Control.Invoke for avoid cross-threading problem,but I prefer use BackgroundWorker for resolve it, create Form6 on a _field and use progressbar in ProgressChanged event for more information see this page
public void thread()
{
Form6 for6=null;
Application.OpenForms[0].Control.Invoke(delegate{
for6 = new Form6();
for6.Show();
});
}
I have the following code to update the progress bar in async fashion and i notice
its async behaviour through the call to MessageBox.In this case it works perfectly
but when i give a sleep of 1s(1000) the MessageBox doesnot pops up and the the complete progress bar fills at once.
Kindly tell why this is happening.
private void button1_Click(object sender, EventArgs e)
{
Update_Async async = new Update_Async(Update_Async_method);
progressBar1.BeginInvoke(async,10);
MessageBox.Show("Updation In Progress");
}
public void Update_Async_method(int a)
{
this.progressBar1.Maximum = a;
for (int i = 1; i <= a; i++)
{
progressBar1.Value = a;
Thread.Sleep(10);
//Thread.Sleep(1000);
}
}
Try Update_Async.BeginInvoke(async, 10) instead if you want the delegate to run asynchrnously but, you'll have to cross thread checking on the update to the progress bar.
In response to your comment, very similar to what you are doing already,
void UpdatingFunction(int value)
{
if (this.progressBar.InvokeRequired)
{
this.progressBar.BeginInvoke(UpdatingFunction, value);
return;
}
// Invoke not required, work on progressbar.
}
This also explains what the Invoke methods on controls are for.
Delegate.BeginInvoke will run a method in a thread once and then dispose it. It is a poor choice if you want to repeatedly do some work in a thread and return intermediate results. If that is what you want, you should use BackgroundWorker. Highly abbreviated snippet:
BackgroundWorker bw;
YourFormConstructor()
{
...
bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += BackgroundCalculations;
bw.ProgressChanged += ShowBackgroundProgress;
}
private void button1_Click(object sender, EventArgs e)
{
bw.RunWorkerAsync(10);
}
void ShowBackgroundProgress(object sender, ProgressChangedEventArgs e)
{
this.progressBar.Value = e.ProgressPercentage;
}
static void BackgroundCalculations(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
int max = (int)e.Argument;
for (int i = 0; i < max; i++)
{
bw.ReportProgress(i * 100 / max);
Thread.Sleep(10);
}
bw.ReportProgress(100);
}
}
I've a question on how the backgroundworker would actually work. I'm not too sure how it would work with different methods.
So for example I have the following code (adapted from http://broadcast.oreilly.com/2010/06/understanding-c-using-backgrou.html for illustration purposes):
private void getDateTime()
{
DateTime startTime = DateTime.Now;
double value = Math.E;
while (DateTime.Now < startTime.AddMilliseconds(100))
{
value /= Math.PI;
value *= Math.Sqrt(2);
}
}
private backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for(int i=0; i <= 100; i++)
{
getDateTime();
backgroundWorker1.ReportProgress(i);
}
}
This would continually calculate the DateTime using the backgroundworker thread. Now I'm assuming that the for-loop within the backgroundworker's DoWork method would run the calculation, report the progress, and then reiterate the loop (please do correct me if I've misunderstood).
Now assuming I have another method, which parses a (presumably large) DataTable and returns another DataTable, using it in the backgroundworker would mean I'd be parsing the DataTable over and over (theoretically. again, correct me if I'm wrong)? If this is how the program would flow, is there a more efficient method to achieve the DataTable parsing without having to reiterate the loop, but still being able to use backgroundworker?
This is intended for a Windows Form, and the UI tends to freeze up when I parse the DataTable(s). I've heard that backgroundworker could help me get around the freezing of the UI, but I find it highly inefficient to have to re-parse the entire DataTable over, and over. However I'm not sure of any other way I can report progress without a for-loop either.
EDIT:
I've figured out a temporary solution. It works, but I still have no way of reporting progress.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
int rows = (int)e.Argument;
int count = 0;
t3 = runComparison();
for (int i = 0; i <= rows; i++)
{
count++;
int current = count / rows;
backgroundWorker1.ReportProgress(current * 100);
}
e.Result = t3;
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
MessageBox.Show("An unexpected error has occurred. Please try again later.", "ERROR!", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
dataGridView1.DataSource = e.Result;
}
}
I'm aware my for-loop doesn't do anything. However, the "runComparison" already consumes a lot of time. As such, progress doesn't get reported until an entire run-through if it's in the for-loop. Alternatively, maybe I could use a label or messagebox instead of the progress bar. Not too sure how I'm supposed to achieve this.
Try to do this:
//make sure background worker report progress
backgroundWorker1.WorkerReportsProgress = True;
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
progressBar1.Refresh;
//if your progressbar is on a status strip, you need to refresh the status strip instead
}
I've come up with a rather inelegant solution. But it satisfies the requirement for user-friendliness so... Whatever works :P
Thanks for all the feedback, I appreciate it.
Anyway, here's the code:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
backgroundWorker1.ReportProgress(1); //to invoke ProgressChanged
t3 = runComparison(); //run intensive method
e.Result = t3; //return DataTable
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Visible = true; //display a label
textBoxProgress.Visible = true; //display the progress bar
//change the progress bar style
//the Marquee style is constantly running, so it could show users the process is working
//not too sure how to explain it, you'll have to try it out.
progressBar1.Style = ProgressBarStyle.Marquee;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
MessageBox.Show("An unexpected error has occurred. Please try again later.", "ERROR!", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
else
{
//reset the display and update the DataGridView
progressBar1.Visible = false;
textBoxProgress.Visible = false;
progressBar1.Style = ProgressBarStyle.Blocks;
dataGridView1.DataSource = e.Result;
}
}