c# backgroundWorker not raising ProgressChanged or RunWorkerCompleted events - c#

Using C# .NET 3.5.
It seems I'm not the only one, since a search seems to bring up a lot of similar questions, however reading through them I seem still to be missing the solution.
I'm raising an event with a button click on my UI, which is then supposed to start a backgroundWorker to get some time consuming work done - in this case, I want it to collect information from a form and a) write to an xml file as well as b) insert the information into a remote database.
This is exactly what the backgroundWorker was designed and intended to do I believe.
Here is the event raising code.
private void btnFinish_Click(object sender, EventArgs e)
{
clkFinish.setInstant();
curAct.setTimeFinish(DateTime.Now);
btnStart.Enabled = true;
bgwDataWorker.RunWorkerAsync();
for (int i = 0; i < 20; i++)
{
Console.WriteLine("Thread a: " + i);
Thread.Sleep(100);
if (i == (20 - 1))
{
Console.WriteLine("Finished");
}
}
}
As you can see, I have some code there which I've used as a counterbalance to the background worker code, which is here:
private void bgwDataWorker_DoWork(object sender, DoWorkEventArgs e)
{
Console.WriteLine("Running in a different thread now");
int count = 0;
for (int i = 0; i < 21; i++)
{
Console.WriteLine("Thread b: " + i);
Thread.Sleep(100);
(sender as BackgroundWorker).ReportProgress(5 * i, null);
if (i == (21 - 1))
{
count = i;
}
}
e.Result = count;
}
So far, everything seems to be working up to this point.
My problem is that when the code in the DoWork method is finished, nothing is happening. Here are the ProgressChanged and RunWorkerComplete methods.
private void bgwDataWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Console.WriteLine(e.ProgressPercentage);
}
private void bgwDataWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Console.WriteLine("Result is " + e.Result.ToString());
}
This has me baffled.
I've tried running the examples from MSDN and I'm experiencing the same problems. RunWorkerCompleted and ReportProgress events are simply not being raised for some reason and I don't understand why.
Thanks for reading.
Alan

I had the same problem and I just figured out the answer.
In the properties of the backgroundworker, make sure that ProgressChanged and RunWorkerCompleted list the name of their functions (mine are called 'backgroundWorker1_ProgressChanged' and 'backgroundWorker1_RunWorkerCompleted' respectively).

You didn´t miss to set these variables, did you?
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
Also it could be an exception in the DoWork-Method, that isn´t handled.

Inksmithy, you mentioned you have renamed the BackgroundWorker. Please note that when you do this, the Forms Designer does NOT automatically change the reference to the old DoWork and RunWorkerCompleted methods. So in your Forms designer, click on your BackgroundWorker, then click on the Events icon (lightning bolt) in the Properties Window, and double-check that it is pointing to the correct Event Method Names.

Set WorkerReportsProgress property to True.

You've left out a vital bit - a boolean flag to allow it to raise the events
bgwDataWorker.WorkerReportsProgress = true;
bgwDataWorker.WorkerSupportsCancellation = true;

I had one project [A], (built a while back) that had the progress bar working. The current app [B] is not reporting progress. For some reason app [A] had the progress and work completed event handlers wired up. So, I hooked up the event handlers in app [B] and it is working. The handlers are copied into the designer.cs partial class file. (Assuming they could go in the form.cs partial class).
//
// backgroundWorker1
//
this.backgroundWorker1.WorkerReportsProgress = true;
this.backgroundWorker1.WorkerSupportsCancellation = true;
this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork);
this.backgroundWorker1.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(this.backgroundWorker1_ProgressChanged);
this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);
//
hope this helps. Don't know why the app [A] was wired up by using the toolbox to drop the background worker on the form design window. Using VS2012 and Windows 7.
Also, I am calculating the progress percentage and for some reason the number of items in the listbox times the number of selected checkboxes exceeded 100% - it threw and error. So, I will cap the percentage at 99 and make that part of my logic for the progress percentage being reported back to the event.

I had this issue recently. It was caused because I had the main thread sleeping until the background thread was finished, which blocked the update from executing.
I solved this by wrapping my object (containing worker threads) into another thread so the progress update could execute, change the values in the main thread allowing my sleeping thread to continue.

Related

WPF TextBlock shows all log messages only after all work is done

When user clicks on Execute button, I want to do some stuff and output log messages to TextBlock progressively - so user can see what is currently happening.
The problem is that my TextBlock changes it content after all work is finished (too late). How can I force WPF to repaint itself during process ?
Code looks like this:
private void btn_execute_Click(object sender, RoutedEventArgs e)
{
.... stuff ....
}
I have tried adding output_log.InvalidateVisual(); after any change to TextBlock, didn't work as expected.
If you run synchronous code in a Click handler of a Button, this code is being executed in the Dispatcher thread and thus prevents the Dispatcher from running any other code like displaying the changes of your messages in a TextBlock.
There are (at least) three possible ways to solve this issue.
First, you can run your Execute code in another Thread, Task or async event handler and set the Text using the Dispatcher:
private async void btn_execute_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < 100; i++)
{
// Simulate doing some stuff...
await Task.Delay(100);
// Thanks to async/await the current context is captured and
// switches automatically back to the Dispatcher thread after the await.
output_log.Text += i + ", ";
// If you were using Task.Run() instead then you would have to invoke it manually.
// Dispatcher.Invoke(() => output_log.Text += i + ", ");
}
}
The main advantage is that you are not blocking the Dispatcher - which is highly recommended for everything you do.
Second, you can keep doing your Execute code in the Dispatcher, but then you have to "flush" the Dispatcher every time when you want to refresh your text, so that it can handle all waiting UI actions:
private void btn_execute_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < 100; i++)
{
// Simulate doing some stuff...
Thread.Sleep(100);
output_log.Text += i + ", ";
Dispatcher.Invoke(DispatcherPriority.Background, new Action(() => { }));
}
}
This is certainly possible but I really wouldn't recommend it.
Or third,
you can use MVVM for your architecture,
run your Execute code in an async event handler (or Command),
change only the LogText property of your ViewModel and
use data binding to bind the TextBlock.Text to this MyLogViewModel.LogText property.
Unfortunately I can't give you a quick sample code for this scenario, but it's surely worth thinking about it because MVVM is just a really natural architecture for any kind of WPF application.

C# Invoke button control on separate thread

I have seen a lot of questions about how to edit controls on c# form from a different thread but none make much sense to me. I understand that you can not change any UI from another thread than it's main. To make this work you have to use invoke and from there safely edit the control?
I have a button that starts writing in a file and the moment you press the button the button itself gets disabled so you can not start multiple threads that do exactly the same. When the writing is done I want the button to be available again but I can not get it working on this other thread.
I have this as the Generate_Click event from the form.
private void Generate_Click(object sender, EventArgs e)
{
Generate.Enabled = false;
int x = 512;
int y = 512;
MBrot mbrot = new MBrot(x, y);
PB_Update lb = new PB_Update(0, y, Generator_PB, Generate, mbrot, this);
lb.Start();
}
And this is in PB_Update.cs the ThreadWork() function, when the while loop is done the writing to the file is done and so is the thread so its ended and given a messagebox with "finished" now as last the button needs to be enabled again.
public void ThreadWork()
{
while (true)
{
if (currValue_ >= maxValue_)
break;
ThreadTick();
}
mb_.StopBrot();
t_.Interrupt();
MessageBox.Show("Finished!");
Generate_.Enabled = true;
}
For WinForms you can execute directly on the thread which the control was created on through the Control.BeginInvoke method, you can use Control.Invoke as well but, Control.BeginInvoke is preferred for UI operations.
public void ThreadWork()
{
while (true)
{
if (currValue_ >= maxValue_)
break;
ThreadTick();
}
mb_.StopBrot();
t_.Interrupt();
MessageBox.Show("Finished!");
Generate_.BeginInvoke((Action)delegate()
{
Generate_.Enabled = true;
});
}
Somehow, get a reference to the form that hosts the generate_ button (let's call it myform). Then, at the bottom of your ThreadWork:
myform.Invoke(new Action(() => {
myform.SetGenerateEnabled();
}));
And then inside your form create that method that enables the button appropriately. (I used a method rather than just updating the button directly so that you don't publicly expose the button.)
This executes the commands inside the { ... } on myform's thread, which is a UI thread, because it is UI. At least, that's what I understand. This is how I do all of my UI updating from other threads.
Here's a simple example of a way to kick off an async task that disables a button for 5 seconds and then enables it again. Meanwhile, the rest of the UI is functional.
Note that this async method exists in the same class as your Generate_Click event, and runs on the UI thread. This means that it can enable and disable the button. But the long running task executes on a separate thread, so it doesn't lock the UI.
Hopefully this sample provides you a base to modify for your own code:
private void Generate_Click(object sender, EventArgs e)
{
DisableButton(sender as Button, 5);
}
private async void DisableButton(Button sender, int secondsToDisable)
{
sender.Enabled = false;
// In your code, you would kick off your long-running process here as a task
await Task.Run(()=>Thread.Sleep(TimeSpan.FromSeconds(secondsToDisable)));
sender.Enabled = true;
}

Label not updating in statusstrip with progressbar

I have been developing a windows forms application in vc# express 2013. I have a statusstrip control where i have a label and a progress bar. I want to show the progress of progressbar in label but it is not updating with the progress bar value. it only gets updated when the progress bar is complete i.e. when value is 100 and the job is finished. The strange thing is that the progress bar is updated but the label is not... Any ideas what might be happening here? By the way, I raise a progress event in my own code which is handled and the progress is updated accordingly...
Thanks
I raise the event ProgressTick in MGFDataHandler Class
if ((hundredthValue*progressMultiplier == lineCounter) || (lineCounter==totalLines))
{
//Raise the progress bar event!
EventHandler handler = ProgressTick;
if (handler != null)
{
handler(this, e);
}
progressMultiplier++;
}
I then handle it in windowsform as follows:
void msDataHandler_ProgressTick(object sender, EventArgs e)
{
var progress = (int)((MGFDataHandler)sender).progressMultiplier;
if (progress < 100)
{
this.progressStatusString.Text = "Progress: " + progress + "%";
this.progressBar.Visible = true;
this.progressBar.Value = progress;
}
else
{
this.progressStatusString.Text = "Done";
this.filterMSDataToolStripMenuItem.Enabled = true;
}
}
The function which starts the whole thing is in the windowsform_activated event and MGFDataHandler object is an attribute of the windowsform.
private void GlycoMSPipelineMainFrame_Activated(object sender, EventArgs e)
{
switch (this.whoActivatedMe)
{
case WhoActivatedMe.MSFilterWizard:
{
this.whoActivatedMe = WhoActivatedMe.Me;
foreach (String currentmgffilename in this.currentMGFFileNames)
{
mgfDataHandler.Filter_MS_Data(currentmgffilename);
}
this.populateTreeViews();
break;
}
default:
{
break;
}
}
}
It is the Filter_MS_Data that raises the event.
Try using a background worker (found in System.componentModel) to run the task that you are performing. When updating the label or progress back you will need to check if invoke is required.
Something like this.
If (label.InvokeRequired) {
label.Invoke(new MethodInvoker(delegate
{
label.Text = progress.Value;
});
}
Invoke is required to prevent cross threading operations which in turn can produce race conditions.
Forgive the formatting i typed this out on my phone.
You will need to perform the population using a background worker. I think this will resolve your issue.
Edit:
The background worker provides a do work event which will run on another thread. It also provides a finished event so you know when the task is complete.
I think what is happening here is that your program is performing work on the ui thread which prevents the ui from fully updating.
I do need some more information for a full answer, but the direction in which this is heading to is quite clear to me.
The problem is that you're probably doing everything in the same thread. When you have a big job you need to use a background thread to do the work, and let the main thread (i.e. a UI thread) handle the UI.
This is a big topic and I'm just scratching the surface here, so first of all read the following links and try to create a thread for your job and invoke the update of the progress bar yourself. If not successful, then post a new question with more information.
The links you should read are:
Threads in C#
ProgressBar in C#
I realize this is a very old question, but i found it with the same issue in VS2019. I found that rather than adding a thread, you can call the Refresh method of the container of the label and ProgressBar.
void msDataHandler_ProgressTick(object sender, EventArgs e)
{
var progress = (int)((MGFDataHandler)sender).progressMultiplier;
if (progress < 100)
{
this.progressStatusString.Text = "Progress: " + progress + "%";
this.progressBar.Visible = true;
this.progressBar.Value = progress;
}
else
{
this.progressStatusString.Text = "Done";
this.filterMSDataToolStripMenuItem.Enabled = true;
}
this.progressStatusString.Owner.Refresh();
}

C# Label Text Not Updating

I have the following code:
private void button1_Click(object sender, EventArgs e)
{
var answer =
MessageBox.Show(
"Do you wish to submit checked items to the ACH bank? \r\n\r\nOnly the items that are checked and have the status 'Entered' will be submitted.",
"Submit",
MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question,
MessageBoxDefaultButton.Button1);
if (answer != DialogResult.Yes)
return;
button1.Enabled = false;
progressBar1.Maximum = dataGridView1.Rows.Count;
progressBar1.Minimum = 0;
progressBar1.Value = 0;
progressBar1.Step = 1;
foreach (DataGridViewRow row in dataGridView1.Rows)
{
if ((string) row.Cells["Status"].Value == "Entered")
{
progressBar1.PerformStep();
label_Message.Text = #"Sending " + row.Cells["Name"].Value + #" for $" + row.Cells["CheckAmount"].Value + #" to the bank.";
Thread.Sleep(2000);
}
}
label_Message.Text = #"Complete.";
button1.Enabled = true;
}
This is a test I am creating to port over to my application. Everything works fine but the label_Message.text being set. It never shows up on the screen. It is being set, I did a console.write on it to verify. It's just not refreshing the screen. I get the "Complete" at the end also.
Anyone have any ideas?
You're performing a lengthy operation on the UI thread. You should move it to a background thread (via BackgroundWorker for instance) so the UI thread can do things like repaint the screen when needed. You can cheat and execute Application.DoEvents, but I'd really recommend against it.
This question and answer are basically what you're asking:
Form Not Responding when any other operation performed in C#
The Label doesn't re-paint until you give the UI thread back to the message loop. Try Label.Refresh, or better yet, try putting your lengthy operation in a background thread as other posters have suggested.
This operation is executed in UI thread. UI won't update until it's finished. To make it update during sending you must perform sending in separate thread and update the label from there
This usually happens when you're doing intensive calculations/iterations in the same thread as where the user interface elements are running. To work around this you're going to need to have a separate thread do the work and from there update the label's value accordingly. I'd post a complete source sample but I'm away from my dev machine at the moment.
Just to add to this answer, I ran into an issue with our splash screen form.
We had code like this:
SplashScreen.Initialize(this, SplashScreenImage);
SplashScreen.Show();
// Db init, log init etc.
... Further in our app ...
Application.Run(new MainWindowForm());
The in Initialize(this, SplashScreenImage); we updated some controls and then we ran refresh on those controls;
public void Initialize(this, SplashScreenImage)
{
...
lblVersion.Text = GetVersionString();
lblEnv.Text = GetEnvironmentString();
// Refresh (does not work)
lblVersion.Refresh()
lblEnv.Refresh()
}
Unfortunately, this does not work. The problem here was that although we call control.Refresh() explicitly, form.show() was called after we called control.refresh. This does not work.
The fix was simple:
SplashScreen.Show(); // First call show
SplashScreen.Initialize(this, SplashScreenImage); // Now contorl.Refresh() works
I know this question is old but I had this same issue. I tried Refresh() and many other things but nothing worked. If I put the text into a Messagebox.show then it worked in the message box but not the form so I knew I had the data. As I had people waiting to use the app I was getting desperate and was just about to do away with the class temporarily to get it working when I thought of trying Invoke. So I tried
Invoke(new Action(() =>
{
lbltxt.Text = text;
}));
For now it works but still not sure if this is a long term fix or just a plaster till I find a better solution.

Can BeginInvoke interrupt code already running on the UI thread?

Suppose I have some code which is running in the UI thread, which spawns a new thread to do work in the background, and then goes on to do UI work. When the background work is done, BeginInvoke is called from the new thread. If the UI work is still going on, will the callback given to BeginInvoke interrupt the UI work, or will it wait?
Code example - add a button called button1 to a form and add button1_Click to its click event, and then add this to the form:
bool continueWork = true;
private void button1_Click(object sender, EventArgs e)
{
Thread workThread = new Thread(performBackgroundWork);
workThread.IsBackground = true;
workThread.SetApartmentState(ApartmentState.STA);
workThread.Start();
for (long i = 0; i < long.MaxValue; i++)
{
if (!continueWork)
break;
button1.Text = i.ToString();
}
}
private void performBackgroundWork()
{
Thread.Sleep(1);
button1.BeginInvoke(new MethodInvoker(stopUIWork));
}
private void stopUIWork()
{
continueWork = false;
}
What is button1's text after it is clicked?
BeginInvoke adds the delegate to a queue (the message queue to be exact). So the answer is no, they won't get interrupted. Your button click handler is in fact executed due to a message in the message queue as well.
EDIT: Sorry, I forgot to answer your second question, but you can probably figure that out by yourself. The button text will be long.MaxValue - 1. You would probably want to execute stopUIWork without using BeginInvoke.

Categories

Resources