I have a very basic requirement. While the background work in progress, I want to show a progress bar. I used the visible property to manage the appearance of the progress bar. Progress bar is itself a user control which is instantiated in the main frame.
In the background worker, I update the listview of main frame using Invoke API. What I was expecting was that progress bar should be working while list is being populated, but I found that timer of the progress bar user control is not being called.
Here is the code snippet:
// Assigning back ground work
this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.FetchDataFromDB);
this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.DoneWork);
FetchDataFromDB API is called by background worker, which in turn call the PopulateList API of the main frame.
private void FetchDataFromDB(object sender, System.ComponentModel.DoWorkEventArgs args)
{
if (this.InvokeRequired)
{
this.Invoke(new M_Delegate(() => this.PopulateList()));
}
}
private void DoneWork(object sender, RunWorkerCompletedEventArgs e)
{
// Disable progress bar
circularProgressBar_usercontrol1.Visible = false;
}
private void PopulateList()
{
..... Populate listview of the main frame
}
This is how I invoked the background worker.
// Making progress bar visible
circularProgressBar_usercontrol1.Visible = true;
backgroundWorker1.RunWorkerAsync();
circularProgressBar_usercontrol1 has the timer, which control the progress bar. I added a breakpoint at timer1_Tick API of the timer, and I found that this API is invoked only after background worker job is done.
Due to this, timer is not working during background worker and hence, my progress bar is not working correctly.
Please tell me the issue with my code.
Thanks in advance !!
You run all the code to read from DB on the main thread because you immediately schedule work back to main thread.
The goal of using worker thread is to run slow operation on background thread and than quickly send completed result to UI thread (note that you need also avoid giving results of LINQ queries as they use lazy execution and may still run slow code on target thread - see .ToList call in the sample below):
private void FetchDataFromDB(object sender, System.ComponentModel.DoWorkEventArgs args)
{
// slow reading from DB on worker thread
var listFromDb = this.ReadDataFromDb().ToList();
// send populated result to main thread
if (this.InvokeRequired)
{
this.Invoke(AddItemsToUiList(listFromDb)));
}
}
Related
In some games, there is a splash screen that downloads content from a server. It might give some tips while you're waiting. I'm doing something similar, only that the loading happens really quick, but I want it to wait a few more seconds.
When the user first loads my application, it has a screen with a progress bar. At the moment, it checks if the server is online. If it is, it says "Connected!" However, it immediately fades out my controls. I want it to wait about 5 more seconds so the reader can read it. Then fade the controls out.
private void frmMain_Loaded(object sender, RoutedEventArgs e)
{
// Start background worker
m_bgWorker = new System.ComponentModel.BackgroundWorker();
m_bgWorker.ProgressChanged += M_bgWorker_ProgressChanged;
m_bgWorker.WorkerReportsProgress = true;
m_bgWorker.RunWorkerAsync();
m_bgWorker.ReportProgress(100);
}
private void M_bgWorker_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
progConnect.Value = 0;
if (SystemBO.IsOnline())
{
lblConnection.Content = "Connected!";
}
progConnect.Value = e.ProgressPercentage;
// TODO: Wait 5 seconds here...
// Fade out controls
lblTitle.BeginAnimation(Label.OpacityProperty, doubleAnimation);
progConnect.BeginAnimation(Label.OpacityProperty, doubleAnimation);
lblConnection.BeginAnimation(Label.OpacityProperty, doubleAnimation);
}
Note:
I tried System.Sleep(), but that made no difference. I understand why. The idea is the same though: I want the background worker to sleep for 5 seconds before completing.
Solution:
I added a few more events: DoWork and RunWorkerCompleted.
Then I added this code:
private void M_bgWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
for (int i = 0; i < 100; i++)
{
m_index++;
System.Threading.Thread.Sleep(40);
m_bgWorker.ReportProgress(m_index);
}
System.Threading.Thread.Sleep(3000); // wait 3 seconds to read
}
It worked. The progress bar animates quite smoothly.
You shouldn't do the fade-out in the ProgressChanged handler. That handler really should only be used to update progress information like text and a percentage bar.
Your fade-out logic should be moved to the separate RunWorkedCompleted event handler. Here you can let the thread sleep/await a scheduled task for a while before beginning to fade out the controls. (Using async/await over Thread.Sleep() here has the advantage of not blocking the UI thread while waiting, so the UI still remains responsive while waiting for the time to pass.)
It also ensures that the code is called at the very end and not every time the worker reports any kind of progress. And it's also cleaner because it allows you to handle different termination states by checking the RunWorkedCompletedEventArgs:
If e.Error is set, an exception occurred in the worker thread.
If e.Cancelled is true, the worker thread got cancelled by calling CancelAsync() on the worker. (Only possible if WorkerSupportsCancellation is set to true and only useful if code in the DoWork handler actually checks for the cancellation flag)
Otherwise everything went okay.
Side note:
I am assuming that your code is not a full example, because you have no actual handler assigned to the DoWork event. So right now it would not do anything. And calling ReportProgress() right from the main thread is wrong as well. That method is designed to be called from within the DoWork event handling method in order to let the asynchronous thread report status updates back to the UI, since those events are handled on the main thread.
rather than the last line in frmMain_Loaded:
m_bgWorker.ReportProgress(100);
I would put a for loop with Sleep(), for simulating some extra processing:
for (int i=0; i<=100; i+=10)
{
if (SystemBO.IsOnline())
i = 100;
Sleep(1000);
m_bgWorker.ReportProgress(i);
}
I have a Console app that displays a WinForms Form.
In the Form, the user clicks button 1 and it runs a long subroutine. I want to have a button 2 that can kill the subroutine at any point. However, the UI freezes when I click button 1 until the subroutine has finished. How can I get the UI to not freeze?
Your long-running code is blocking the UI thread, so you can no longer click the second button, nor interact with the UI in any way until the code is finished executing.
You'll need to move your long-running code to a separate thread. There are various (and newer) ways of doing this, but one way is the BackgroundWorker. It's pretty easy to learn, and wraps some nice functionality, like cancelling the thread.
Here's a short WinForms app to demonstrate. You have to explicitly enable the ability for the thread to be cancelled. In this example, the while loop continues indefinitely, but checks every 100ms to see if there's a request for it to be cancelled. When you click the second button, the cancellation request is sent, and the thread ends.
public partial class Form1 : Form
{
private BackgroundWorker bg;
public Form1()
{
InitializeComponent();
bg = new BackgroundWorker
{
WorkerSupportsCancellation = true
};
bg.DoWork += (sender, args) =>
{
while (true)
{
Thread.Sleep(100);
if (bg.CancellationPending)
break;
}
MessageBox.Show("Done!");
};
}
private void button1_Click(object sender, EventArgs e)
{
bg.RunWorkerAsync();
}
private void button2_Click(object sender, EventArgs e)
{
bg.CancelAsync();
}
}
Following up on chouaib's comment, another nice thing about using the BackgroundWorker in a WinForms environment is that you can drop and drop it onto your designer, similar to a Menu, Timer, etc. You can then access its members in the "properties" panel, setting "WorkerSupportsCancellation" to true, subscribing to events, etc.
From your comment:
"is there a way to run this background process and be able to update the main userform? I keep getting the "cross-thread operation not valid control accessed from a thread other than the..." I want to run the long-running background operation, and have it update the main UI with text in a label (like a percentage of its progress)"
If you want to update the UI while the thread is running, you should do that from the ProgressChanged event. First, enable that option and subscribe to the event:
bg.WorkerReportsProgress = true;
bg.ProgressChanged += bg_ProgressChanged;
Then call ReportProgress() when you want to update the UI. You could pass back a percentage complete and some text, for instance:
bg.ReportProgress(50, "Almost there...");
Finally, update the UI from inside the event:
void bg_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var message = e.UserState.ToString();
var percent = e.ProgressPercentage;
lblStatus.Text = message + " " + percent;
}
You need to make it multithreaded as suggested in the comments. The older way of doing this was manage your own thread. Then along came the background worker (cheap and easy). Now a days you have other options such as the Task Library.
Remember - anything the runs on the UI thread prevents the UI from sending and receiving events until that operation is finished.
Look into the BackgroundWorker component
I am facing an issue with progress bar in background worker.
public void UpdateDevices(object sender, EventArgs e) {
bw_1.RunWorkerAsync();
Thread.Sleep(100);
WebSwitchHandler.GetDeviceStats(NewRoom);
//Above line opnes a socket and takes 5 seconds to return back//
}
private void bw_1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i <= 100; i++)
{
Thread.Sleep(100);
bw_1.ReportProgress(i);
}
}
private void bw_1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
The progress bar updates only after the socket has returned. The socket class has no threads, it blocks the entire gui for the 5 seconds.
Why doesnt this work? and suggestions how I should handle this with GUI.
The problem is because the progress bar is being updated by the UI thread. But the UI thread is "busy" waiting for the GetDeviceStats function to complete. So it cannot handle the progres bar update until it becomes free again.
If the GetDeviceStats function is a long running process (which to me it is) then you would be best including that work within you background worker thread. However, you still wont be able to report the progress (and therefore update the progress bar) until the call has once again finished.
Some possible solutions to that would be to either have GetDeviceStats report progress as it completes (I am not sure what that function actually does, so this may not be an option), OR if you need to "fake" the progress then use a second background thread or a timer.
Personally, I would just set the progress bar style to "Marquee" so the user knows the program is busy doing something, and then run the GetDeviceStats function on a single background worker.
Actually, You need two background worker to achieve the GUI working for you with progress bar. one you already have to update the progress bar.
Second one you can use to call the WebSwitchHandler.GetDeviceStats(NewRoom); as async operation so that your UI thread doesn't get blocked. On completed of this BG worker you can notify the first one to finish immediately rather waiting for a fixed time always. This way you can also optimize your wait time.
I am attempting to add a progress bar (MARQUEE style) in my c# excel add in. The progress bar appears but it does not indicate any progress until the function is finished executing, meaning that it remains frozen.
These are the functions in the Windows Form Class:
public void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 100; i++)
{
Thread.Sleep(100);
//run in back thread
backgroundWorker1.ReportProgress(i);
}
}
//call back method
public void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
//call back method
public void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressBar1.Value = progressBar1.Maximum;
}
This is how I call the function from the add in button:
private void buttonClicked(object sender, RibbonControlEventArgs e)
{
AddInPlanView.Form1 pBar = new AddInPlanView.Form1();
pBar.Visible = true;
pBar.backgroundWorker1.WorkerReportsProgress = true;
pBar.backgroundWorker1.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(pBar.backgroundWorker1_ProgressChanged);
pBar.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(pBar.backgroundWorker1_DoWork);
pBar.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(pBar.backgroundWorker1_RunWorkerCompleted);
pBar.backgroundWorker1.RunWorkerAsync();
FUNCTIONTORUN();
pBar.Visible = false;
}
My code now attempts to put the progress bar on a background worker. I know I should switch this so that FUNCTIONTORUN() should be on a background worker, but how do I go about doing that? Since it is a marquee progress bar I do not need to update the value of the progress bar at all, it just needs to run until FUNCTIONTORUN() ends. Note that FUNCTIONTORUN() updates the cells of an Excel worksheet.
The progress bar is initialized using the Visual Studio designer. The name of the ProgressBar object in the form class is progressbar1.
to start a marquee style progress bar, type
progressbar1.Style = ProgressBarStyle.Marquee;
progressbar1.MarqueeAnimationSpeed = 100;
to stop the marquee type
progressbar1.MarqueeAnimationSpeed= 0;
Source: http://social.msdn.microsoft.com/forums/en-US/winforms/thread/9e51ad57-988e-4c17-98f0-cd2a8abec503/
FUNCTIONTORUN(); // make your UI thread busy.
System.Windows.Forms.Application.DoEvents(); // might help in this case.
In most of this sort of cases DoEvent doesn't help (according to my experience). So, it's quite difficult to get process bar to work smoothly.
When the Background worker completes just stop the progress bar.
Inside of this function pBar.backgroundWorker1_RunWorkerCompleted just call the Stop function for the progress bar.
I believe you have to put FUNCTIONTORUN() into sepratae thread, and still it's execution call
ReportProgress method, in order to raise backgroundWorker1_ProgressChanged where you can update your porgress bar value. I suppose, as I don't see it in the code provided, you don't call ReportProgress function somewhere in the code.
EDITED: saw ReportProgress function.
So move your function into separate thread and update the value of progress bar into the main thread.
Hope this helps.
Regards.
Your FUNCTIONTORUN() blocks the UI thread.
Spread some System.Windows.Forms.Application.DoEvents() inside your long-running functions to process events (so the UI updates).
This, as mentioned, will update the UI and let the application respond to other events.
You might want to disable the button that triggers this long-running function, or other long-running functions.
You could create an additional background worker with a DoWork that contains the call to your FUNCTIONTORUN() inside of it and a RunWorkerCompleted that makes your bar stop or go invisible. Then, instead of calling FUNCTIONTORUN(), call backGroundWorker2.RunWorkerAsync();
This should prevent the function from freezing your UI. I ran into this issue last summer doing nearly the exact same thing, and fixed it using backgroundworkers.
I am stuck and was hoping someone could help me.
I have made a class/gui with a loading bar set to marquee so that when a task is being carried out i could display it to the user.
In one of my gui classes, in the constructor on the first line i am making a new instance of this class and then doing
LoadingBar bar = new LoadingBar();
Thread thread = new Thread(bar.Show);
thread.Start();
However, even tho the main programme thread is going off doing some more intensive stuff, this gui still seems to freeze, even if i use backround worker.
Is there anything wrong with the approach i have mentioned and if so what do i need to change?
Thanks
You need to reverse your method. The GUI needs to stay in the main thread while the work is done in a "worker thread" (typically a BackGroundWorker). Then the worker reports back to the GUI which then updates.
You'd better do the opposite. Make your intensive work in the thread (or a background worker), and show the wait screen in the main application thread.
You need to use a BackgroundWorker. Drag one onto your form, click backgroundWorker1 and set the WorkerReportsProgress property to True
Then goto the events (via the properties window) and attach handlers for
DoWork, this is where all the work that is represented by the progress bar. You will "report progress" via this and the background worker will make sure ProgressChanged is called on the UI thread.
ProgressChanged, this is where you update the UI based on progress and state data reported to the method
DoWork event looks something like this
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
var userState = new StateClass();
while (working)
{
// TODO: do work here
// update the state surrounding this task via userState
userState.property = "some status";
// report the progress so that backgroundWorker1_ProgressChanged gets called
this.backgroundWorker1.ReportProgress(percentComplete, userState);
}
}
ProgressChanged event looks like this
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// e.UserState contains the state data you passed to ReportProgress,
// you have to cast it to the right type though, since its of type object
var userState = (StateClass)e.UserState;
int progress = e.ProgressPercentage;
// TODO: report progress to the UI with the above variables
}
Now all you have to do is tell the background worker to do work by calling
this.backgroundWorker1.RunWorkerAsync()
I don't think you're doing all your work within the background worker or the GUI wouldn't be freezing. We'll need to see more of your code or you'll need to have another look at background worker examples to see what could be wrong.
EDIT
Add a call to System.Threading.Thread.Sleep(1); after your report progress call