This question already has answers here:
Async Progress Bar Update
(3 answers)
Closed 3 years ago.
I have a form (called Form1) and I have created a status strip (called toolStripProgressBar1), with a label and a progress bar. I have a function that is called when you press a button and gets some data and processes it, which works well.
I want to provide the user with some information as to its progress so I want to set the label and progress bar but I can't get it to set
private static async Task GetSurvey(string surid)
{
Form1.toolStripProgressBar1.Value = 10;
It will be a lot easier if you don't do it as static; doing the work in a method that has access to the form instance variables would make this quite trivial. Be aware though that your progress bar is (== should be) updated by the windowing thread, and most things like button click event handlers are actioned by that same thread. This means that you click your button and the thread that created the progressbar gets busy doing your long running code...
Now, you're running your task async and this is good, just make sure that you await any lengthy operations so that the windowing thread can hand off to a background process at that point and go back to its main job (updating the UI) otherwise you won't see your progress bar updating very effectively. Don't try to update form controls from code that is run on a different thread to the main windowing thread.
If you're not clear on how async/await works, visualise it that upon encountering await within some method block, the thread that is executing the code will create a background process to complete the await'd section, and it itself will travel back up the call stack til it reaches the first method not marked async and proceed from there. In practical terms in a windows forms app you want all your code that "does stuff" in response to a button click to be marked async so you can release the windowing thread out of your code context entirely and back to doing its job of keeping the UI responding, whenever you use await
An alternative way of doing this might be tonise a BackgroundWorker - you attach a DoWork event handler that does the work, and as part of this code it should regularly call the ReportProgress method, passing in an int percentage completion. The ProgressChanged event handler on the BGWorker is used to set the percentage bar/Uu elements -critically you need to know that DoWork runs on a thread that is NOT the windowing thread (and windows controls must not be accessed from a thread other than the thread that created them, usually the windowing thread) so we don't access the progress bar directly from DoWork- instead we call ReportProgress and that causes the ProgressChanged event to fire, and the BGWorker deliberately arranges things so the code in that event handler IS run on the windowing thread(actually the thread that creates the worker but this should be the same)
Related
I have a gui. When I press a button my gui interacts with a software. It takes some seconds. During this time I want a dialog box, pop up or some thing like that to appear infront of my gui which tells the user to wait (with a message). When interaction of gui with software finishes the pop up automatically closes and user can again normally interact with gui.
Is there any way to do that ?
The trick is to spin off a thread so as not to tie up the UI thread. This is typically achieved via a BackgroundWorker.
There's a walkthrough for setting all this up on codeplex. The loading form closes when the backgroundworker is complete.
Here is briefly how it can be done using the BackgroundWorker component.
Put a BackgroundWorker onto your Form, then in the button's Click handler show your popup indicator Form above the current form, and start your worker with its RunWorkerAsync method. Handle the workers DoWork event, and it the handler, run the long running task. Also handle the worker's completed event (not sure now how it's called exactly), and in that hide your popup form. You can track the operation result in the DoWork event eventargs (Result property), and also you can catch any exceptions during the long running task with the completed event eventarg's Error peroperty. The operation progress can be reported in the DoWork handler with the worker's ReportProgress method, and it can be catched in the GUI with the worker's corresponding event.
You could also set mouse cursor to wait before long running operation
this.Cursor = Cursors.WaitCursor;
and than back to normal, then it's finished
this.Cursor = Cursors.Default;
I have a modal dialog with a cancel button only which pops up when the user clicks on a button. Aftre the modal dialog pops up, I would like to start a long process which monitors external event. If the event happens, then the dialog will be closed automatically. The user can cancel the monitoring process by clicking the cancel button.
I assigned the process start to the Shown event
private void ProceedForm_Shown(object sender, System.EventArgs e)
{
controller.StartSwiping();
}
The process itself is a loop
public void StartSwiping()
{
Status status;
do
{
status = CallForFeedback();
} while (status == Status.Pending);
form.DialogResult = DialogResult.OK;
form.Close();
}
The process starts fine, but the dialog does not pop up, so the user can non cancel the process. I also tried to assign the start to the Load event, but nothing changed.
Is there any way to Show the dialog and after that start the process?
Thanks
Your problem is that you are doing everything in the UI thread. You need to put you status monitoring loop in a separate thread so that the UI thread can remain responsive.
There are several ways you can do this, but one easy place to start is with the BackgroundWorker class
Use a Task to do your LongRunning events:
CancellationTokenSource _cancelationTokenSource = new CancellationTokenSource();
new Task(() =>
{
//Do LongRunning task
}, _cancelationTokenSource.Token, TaskCreationOptions.LongRunning).Start();
Use the _cancelationTokenSource to cancel the task when needed.
I would move the long running code onto a background thread as you are blocking the UI thread, which is why the UI never displays.
Use a background worker class for the controller functionality http://msdn.microsoft.com/en-us/library/cc221403(v=vs.95).aspx
When the work is completed on the background worker (i.e. the event is received) then you can use the following mechanism to callback onto the UI thread:
http://msdn.microsoft.com/en-us/library/ms171728(v=vs.80).aspx
Note: the article says you can turn off the crossthreadexception this would be considered bad practice, instead handle it the correct way using the InvokeRequired check and then invoke method on the windows form.
Others have suggested using a BackgroundWorker, or some other sort of background thread. While in many cases this is appropriate here, there is likely an even better solution. You're not just doing some long running task, you're waiting for something to happen. Rather than constantly polling...whatever it is, you should be using events. There should be an event that is triggered when you are done, and you should subscribe to that event to do whatever you need to do (i.e. close the dialog) when the correct conditions are met.
I have a main GUI application, that does all of it's actual work in a referenced assembly. Right now, I DON'T do the work in a background worker, so it basically locks the main UI while it does it's processing. In my referenced assmbly, I added quite a few events to report back different progress back to the main UI form. On the main UI form, I update different text boxes with the values from those events. My question is, first of all, the processing appears to be much slower when throwing these events. So should I fire the events on a secondary thread (from the referenced assembly)? Should my original call to the referenced (static) assmebly be via a background worker? I'd like to report the different types of progress on a separate thread, just not sure which approach to take to have optimal performance.
Thanks
From your description it sounds like you would benefit from multithreading, as it would help keep the UI responsive.
And the easiest way to do this is to use a BackgroundWorker. Start by working through one of the many samples, then bite the bullet, and come back here if you have any problems.
In response to comment:
The best way to communicate from a BackgroundWorker worker thread to the main thread is to call the BackgroundWorker.ReportProgress method, which takes an optional object parameter userState which you can use to package up the data you want to communicate.
This causes the BackgroundWorker.ProgressChanged event to be raised on the main thread - and the data can be processed without the need for an explicit Invoke.
If you've already implemented events, you'll either have to do some rework to call ReportProgress instead of raising events, or implement some kind of adapter to handle the events and route them to ReportProgress method calls.
You could launch your process (the method on the other assembly) on a different thread, and handle the events raised by it on the main form.
Since the UI cannot be updated by another thread, you should wrap the code of those events on a this.Invoke().
Ex:
private void TheEventRaisedOnAnotherThread(object sender, EventArgs e)
{
_counter++;
this.Invoke(new MethodInvoker(delegate() { TextBox1.Text = _counter.ToString(); }));
}
I have a main form that contains an edit control that occupies the entire form. There is another worker thread that constantly writes log messages to this edit control. Now I want to show a dialog box with just a cancel button while the main UI's edit control is displaying stuff. The problem is that the cancel dialog is non-responsive while the updates are happening behind it and I cannot click on the cancel button. Any idea on how to resolve it. I was thinking of creating another UI thread and show the cancel button from it. Any other alternatives?
EDIT#1
I should clarify that I already use a worker thread to do the work.
DisplayLogs() is in a seperate thread.
DisplayLogs() is called from other threads.
LogMessage is the delegate that points to the method UpdateMessage in main UI.
The control used is a TextBox. I have tried other controls like listview,
richtextboxsand, etc. still the same result.
Worker Thread
void DisplayLogs()
{
lock (this)
{
while (logQueue.Count > 0)
{
string logMessagemessage = logQueue.Dequeue();
LogMessage(string.Concat(logMessagemessage, Environment.NewLine));
}
}
}
Main UI
public void UpdateMessage( string message)
{
if (!txtLog.IsHandleCreated)
{
return;
}
if (txtLog.InvokeRequired)
txtLog.BeginInvoke( new UpdateLogDelegate( UpdateLog), message);
else
txtLog.AppendText(message);
}
The main solution is to offload the expensive code onto a background worker and leave your UI thread responsive for UI actions. Your form can then simply show a modal dialog or something.
MSDN - How to use a Background Worker
In this situation it's necessary to move the majority of the work to a new thread, and clear up the UI thread for cancel messages etc.
You are going about this backwards. The main thread should, in theory, always be available to accept user input. Anything that may block for extended periods of time (heavy computation, database access, network access) should be done in a background thread. The idea is to have the edit control's data being computed and populated by a background thread (BackgroundWorker objects work nicely here) so that the main thread is always available if the user clicks on the cancel button.
Your problem is that the your UI thread is ALWAYS busy. I am saying this assuming that the number of items in logQueue is quite large. The while loop doesn't quit till the queue is empty. So it keeps hitting the UI thread with request for updates.
Also the if (txtLog.InvokeRequired) is kind of pointless because you are always calling the method from a worker thread.
So, since a .net WinForm application has only a single UI, which in your case is too busy to process other notifications, the new window appears stuck (because the paint messages are stuck in the message queue and cannot be processed as it is already flooded with the text box update messages)
You could stick an Application.DoEvents inside your loop which will give the message loop some time to process the pending notifications. However this is kind of a hack, in my opinion, as the UI behavior is sometimes not predictable. It may lead to things like stuttering while moving a dialog, delayed responses to click events etc.
Another point, MessageBox.Show or a Form.ShowDialog (if this is what you are using for the cancel button) is a blocking call. The thread on which you show it WILL hang till you dismiss the dialog. Try Form.Show and set the parent property to the main form.
Another alternative is to add a timer and process only X notifications per Y seconds. This will give the UI thread some breathing room for performing other activities.
I have a simple UserControl for database paging, that uses a controller to perform the actual DAL calls. I use a BackgroundWorker to perform the heavy lifting, and on the OnWorkCompleted event I re-enable some buttons, change a TextBox.Text property and raise an event for the parent form.
Form A holds my UserControl. When I click on some button that opens form B, even if I don't do anything "there" and just close it, and try to bring in the next page from my database, the OnWorkCompleted gets called on the worker thread (and not my Main thread), and throws a cross-thread exception.
At the moment I added a check for InvokeRequired at the handler there, but isn't the whole point of OnWorkCompleted is to be called on the Main thread? Why wouldn't it work as expected?
EDIT:
I have managed to narrow down the problem to arcgis and BackgroundWorker. I have the following solution wich adds a Command to arcmap, that opens a simple Form1 with two buttons.
The first button runs a BackgroundWorker that sleeps for 500ms and updates a counter.
In the RunWorkerCompleted method it checks for InvokeRequired, and updates the title to show whethever the method was originaly running inside the main thread or the worker thread.
The second button just opens Form2, which contains nothing.
At first, all the calls to RunWorkerCompletedare are made inside the main thread (As expected - thats the whold point of the RunWorkerComplete method, At least by what I understand from the MSDN on BackgroundWorker)
After opening and closing Form2, the RunWorkerCompleted is always being called on the worker thread. I want to add that I can just leave this solution to the problem as is (check for InvokeRequired in the RunWorkerCompleted method), but I want to understand why it is happening against my expectations. In my "real" code I'd like to always know that the RunWorkerCompleted method is being called on the main thread.
I managed to pin point the problem at the form.Show(); command in my BackgroundTesterBtn - if I use ShowDialog() instead, I get no problem (RunWorkerCompleted always runs on the main thread). I do need to use Show() in my ArcMap project, so that the user will not be bound to the form.
I also tried to reproduce the bug on a normal WinForms project. I added a simple project that just opens the first form without ArcMap, but in that case I couldn't reproduce the bug - the RunWorkerCompleted ran on the main thread, whether I used Show() or ShowDialog(), before and after opening Form2. I tried adding a third form to act as a main form before my Form1, but it didn't change the outcome.
Here is my simple sln (VS2005sp1) - it requires
ESRI.ArcGIS.ADF(9.2.4.1420)
ESRI.ArcGIS.ArcMapUI(9.2.3.1380)
ESRI.ArcGIS.SystemUI (9.2.3.1380)
Isn't the whole point of OnWorkCompleted is to be called on the Main thread? Why wouldn't it work as expected?
No, it's not.
You can't just go running any old thing on any old thread. Threads are not polite objects that you can simply say "run this, please".
A better mental model of a thread is a freight train. Once it's going, it's off on it's own track. You can't change it's course or stop it. If you want to influence it, you either have to wait til it gets to the next train station (eg: have it manually check for some events), or derail it (Thread.Abort and CrossThread exceptions have much the same consequences as derailing a train... beware!).
Winforms controls sort of support this behaviour (They have Control.BeginInvoke which lets you run any function on the UI thread), but that only works because they have a special hook into the windows UI message pump and write some special handlers. To go with the above analogy, their train checks in at the station and looks for new directions periodically, and you can use that facility to post it your own directions.
The BackgroundWorker is designed to be general purpose (it can't be tied to the windows GUI) so it can't use the windows Control.BeginInvoke features. It has to assume that your main thread is an unstoppable 'train' doing it's own thing, so the completed event has to run in the worker thread or not at all.
However, as you're using winforms, in your OnWorkCompleted handler, you can get the Window to execute another callback using the BeginInvoke functionality I mentioned above. Like this:
// Assume we're running in a windows forms button click so we have access to the
// form object in the "this" variable.
void OnButton_Click(object sender, EventArgs e )
var b = new BackgroundWorker();
b.DoWork += ... blah blah
// attach an anonymous function to the completed event.
// when this function fires in the worker thread, it will ask the form (this)
// to execute the WorkCompleteCallback on the UI thread.
// when the form has some spare time, it will run your function, and
// you can do all the stuff that you want
b.RunWorkerCompleted += (s, e) { this.BeginInvoke(WorkCompleteCallback); }
b.RunWorkerAsync(); // GO!
}
void WorkCompleteCallback()
{
Button.Enabled = false;
//other stuff that only works in the UI thread
}
Also, don't forget this:
Your RunWorkerCompleted event handler should always check the Error and Cancelled properties before accessing the Result property. If an exception was raised or if the operation was canceled, accessing the Result property raises an exception.
It looks like a bug:
http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=116930
http://thedatafarm.com/devlifeblog/archive/2005/12/21/39532.aspx
So I suggest using the bullet-proof (pseudocode):
if(control.InvokeRequired)
control.Invoke(Action);
else
Action()
The BackgroundWorker checks whether the delegate instance, points to a class which supports the interface ISynchronizeInvoke. Your DAL layer probably does not implement that interface. Normally, you would use the BackgroundWorker on a Form, which does support that interface.
In case you want to use the BackgroundWorker from the DAL layer and want to update the UI from there, you have three options:
you'd stay calling the Invoke method
implement the interface ISynchronizeInvoke on the DAL class, and redirect the calls manually (it's only three methods and a property)
before invoking the BackgroundWorker (so, on the UI thread), to call SynchronizationContext.Current and to save the content instance in an instance variable. The SynchronizationContext will then give you the Send method, which will exactly do what Invoke does.
The best approach to avoid issues with cross-threading in GUI is to use SynchronizationContext.