I want to show a new form with a progress bar marquee. This form should be on an other thread, since the action that is called is too big and the UI doesn't update the progress bar if it's on the same thread.
I call a new thread, create my form, but as soon as it appears, the form close...
Here is my code from my main :
frmProgress f = new frmProgress();
Thread t = new Thread(new ThreadStart(delegate() { f.Show(); }));
t.Start();
documentManager.Compile(); //This is my big action
t.Abort();
Here is the code of form :
public frmProgress()
{
InitializeComponent();
}
I would like to know what I did wrong.
Thanks!
You have several problems:
You're showing a form in a non-UI thread. This will only cause problems; it needs to be in the UI thread.
You're performing a long running task in the UI thread; this is blocking UI updates.
You need to be showing the forum in the UI thread, and running the long running task in a background thread.
A great tool for the job here is to use a BackgroundWorker. It will do much of the work for you to ensure the proper code is running in the UI/non-UI threads as it should be. Create a new BackgroundWorker and put the long running task in the DoWork event. Make any simple UI updates that you need to (showing the form) before you start the BGW, and do any cleanup in the Completed event (which will run in the UI). The MSDN page on BackgroundWorker has some nice examples.
Another advantage of using a BackgroundWorker is that you can easily report progress (if you have a good way of knowing your % complete) rather than just having a marquee bar. Just add a handler to the ProgressReported event handler to update the progress bar, and call the ReportProgress method periodically when doing work. See the linked examples for exact code snippets.
Related
For numerous reasons i cannot run the main portion of the code in a background worker, i can instead run a Loading dialog in the background worker but need to close the dialog after the execution of the main code portion. I've implimented the following but I'm not sure how to forcefully close the background worker after the code execution:
LoaderWorker = new BackgroundWorker();
//Loading is the Form
Loading Loader = new Loading("Daten Exportieren");
LoaderWorker.DoWork += (s, args) =>
{
//Show Loading Dialog
Loader.Show();
};
ExecuteMainTasks();
//Here i need to stop the backgroundworker after the method above is complete.
Is there a solution to this with background worker or should i use a different approach to ensure i do not lock the UI thread for the loader. Remember...i cannot execute the main code in the backgroundworker so have to manage this somewhat backwards.
Thanks.
Control.Invoke
Control.BeginInvoke
(Windows Forms)
or
Disopatcher.Invoke
Dispatcher.BeginInvoke
(WPF)
or
CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync
(WinRT 😂)
Calling this from the background worker will execute the passed lambda / delegate in the UI thread. Simply perform progress / finished notifications this way from your background worker.
So, show your dialog in the UI thread, then start the background worker, from which you can use this way of executing code in the UI thread.
Just make sure that you synchronously stop the background worker when the user cacels / closes the dialog.
So I have This app with a MainForm that has a few buttons on it. and the buttons would do time consuming tasks that includes working with MainForm UI. Because some times we need to run multiple instances of this app at the sametime I decided to Create a MainFormHost where it is a form with a tab control which under each tab I create an instance of my MainForm and host it there. And so far everything was ok. The problem is when I click on a button on MAinForm1 it starts working fine but as soon as I click on a button on MainForm2 the process of MainForm1 button gets queued behind the MainForm2 process.
MainForm GetMainFrom(TabPage tabPage)
{
tabPage.Invoke(new Action(() =>
{
mainForm = new MainForm();
mainForm.TopLevel = false;
mainForm.FormBorderStyle = FormBorderStyle.None;
mainForm.Dock = DockStyle.Fill;
_mainForms.Add(mainForm);
tabPage.Controls.Add(mainForm);
mainForm.Show();
}));
}
And then call the method:
var mainFormThread = new Thread(() =>
{
mainForm = GetMainFrom(tabPage);
});
mainFormThread.SetApartmentState(ApartmentState.STA);
mainFormThread.Start();
I cannot change the code inside the MainForm to Invoke things when they want to work with the UI because it is going to be too much of work but I can easily make each button click to be called from a thread/task or...
TIA
In short, you can only have one 'main thread' that can interact with the UI controls. Ever. Period. There is no way to get around this (in standard code, you can draw to your form from a different process, but I won't go into that)
So, what you need to do is arrange your code in a set pattern.
Methods that do background work - this can be done on a separate thread(s)
Methods that Update the UI - MUST be done on the UI/Main Thread
The idea being simple, background work takes a long time, and updating the UI shouldn't.
Because you have 1 or 2 long running actions running ON THE MAIN UI THREAD, this 'blocks the UI' and causes the behavior your are experiencing.
By blocks the UI, what you actually have is the Windows Message pump can't pump. So every single command to resize a window or update a control on a form is a message in the pump. That must run on the main thread. If you do 'work' on that thread, such as connecting to a database, download a file etc., then the pump can't continue, so you get the 'this application is not responding' message.
So, you must download the file or whatever on a thread, and when it's finished, transition to the UI thread, and update the UI, e.g. say finished in a text box.
The way to transition between the two threads (background to main) is to use begininvoke, and you know if this is needed by using 'invoke required' .
From MSDN
// This method demonstrates a pattern for making thread-safe
// calls on a Windows Forms control.
//
// If the calling thread is different from the thread that
// created the TextBox control, this method creates a
// SetTextCallback and calls itself asynchronously using the
// Invoke method.
//
// If the calling thread is the same as the thread that created
// the TextBox control, the Text property is set directly.
private void SetText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.textBox1.Text = text;
}
}
This is a great article and will get you where you need to be:
MSDN about thread safe code and invoke required
While maintaining an old MFC application we have implemented a new progress dialog bar in WPF. The application currently has the UI thread busy with a lot of business operations but changing this is out of scope.
When a string property changes its value (binded to the text of a TextBox) the progress dialog does not get refreshed (only sometimes when the thread is not so busy).
As far as I know as the update of the property is done from the UI thread the thread should be able to update the dialog and repaint it before going on the next thing so I don't get why it's not being updated and how to fix it.
Any ideas?
EDIT: What are the drawbacks of this solution, I have tried it and seems to work fine:
private static Action EmptyDelegate = delegate() { };
public static void Refresh(this UIElement uiElement)
{
uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
}
I found out the solution here:
http://eprystupa.wordpress.com/2008/07/28/running-wpf-application-with-multiple-ui-threads/
Then I created a new thread with the progress dialog.
The application currently has the UI thread busy with a lot of business operations
Well there's your problem. You shouldn't be doing that. You ought to be performing long running operations in a non-UI thread. It's the reason why updates to the UI aren't made until after the long running operation finishes.
You can use a BackgroundWorker to help simplify interactions with a UI while performing a long running task, as it will handle marshaling to the UI thread for the progress updated and completed event handlers.
First, I agree with #Servy, you shouldn't do heavy work in the UI thread.
However, if you cannot do the work in another thread, you can consider spawning another one for your dialog. I don't know how you are calling the wpf window, but this link may give you some clues about how it would be done in C#.
Am relatively new to C# and coding in general. I am trying to write a program that has some logic and that also indicates progress with a progressbar. I am starting a thread in Main() that does all my business logic. It has events that are trigerred at points that I need the progress bar udpated.
The Form object subscribes to the business logic events and has thread safe delegates that are invoked to update the progress bars and text labels.
My problem is that, as the Form is started in the main thread, I have to start the business logic thread before Application.Run(). When the first ProgressUpdate event is trigerred, the Form object still does not exist. I guess a hacky way is to add Thread.Sleep(100) in the second thread, but I don't like that. How do I get around this? Am I on a completely incorrect track? (Am I even making sense?)
Form1 theForm = new Form1();
CreateReport theCreateReport = new CreateReport();
Thread t = new Thread(new ThreadStart(theCreateReport.DoProcess));
t.IsBackground = true;
theForm.Subscribe(theCreateReport);
t.Start();
Application.Run(theForm);
theForm is the form. theCreateReport is where my business logic starts.
You want to use one or more BackgroundWorker objects instead of your business logic thread. This will manage the threading for you as well as giving you a way to provide progress feedback to the main thread.
Maybe you should start your business logic in OnLoad event?
The Form already exists after you invoke the constructor (on the very first line) - it's just not visible yet. So you don't need to worry, everything is initialized when you start the new thread.
In Main, just create the form and Application.Run it. In the Load event of the form, start your thread.
You don't really gain any advantage from doing things the way you're currently doing them. And as you've already found, it creates a timing/sequence problem.
The best way to fix problems is to not have them in the first place.
I would use BackgroundWorker and you can still use your events and delegates with it. This time round you will be wrapping up and firing Background Worker's "ProgressChanged" and "RunWorkerCompleted" events.
And you Form can listen to these events and update ProgressBar accordingly.
BWorker handles switching to GUI Thread and Exception Handling better.
You can initialize BackgroundWorker on Form Load.
I'm writing a simple data UI using standard .Net databinding to a typed DataSet from SQL Server.
I have a reload button which calls Fill on all of the DataAdapters to get new data from the database (in case another user changed the data).
This takes some time, during which the UI is frozen. It must be run on the UI thread or the databinding event handlers throw cross-thread exceptions.
I'd like to show a modal "Please Wait" dialog on a background thread (so that it can be animated) while the UI thread connects to the database.
How can I show a modal dialog box on the non-UI thread?
EDIT: I'm aware that best practice is to run the operation in the background, but I can't do that because of the databinding events.
You should do the opposite. Run your long-running process on a background thread and leave the UI thread free to respond to the user actions.
If you want to block any user actions while it is processing you have a number of options, including modal dialogs. Once the background thread completes processing you can inform the main thread about the outcome
The code running in the databinding events need to be decoupled from the UI, probably using some kind of data transfer object.
Then you can run the query operation in a separate thread or a BackgroundWorker, and leave the UI thread as it was.
Edit: The really quick way to fix this is to get the events to run in their own delegate using InvokeRequired and .Invoke. That will give the methods UI context. My co-worker does this like it's going out of style and it annoys me to no end because it's rarely a good idea to do it this way... but if you want a fast solution this will work. (I'm not at work so I don't have a sample with me; I'll try to come up with something.)
Edit 2: I'm not sure what you're asking for is possible. I made a sample app that created a modal dialog in another thread, and it ends up being modeless. Instead of using a modal dialog, could you use some other control or set of controls to indicate progress change, most likely directly on the same form?
using( var frmDialog = new MyPleasWaitDialog() ) {
// data loading is started after the form is shown
frmDialog.Load += (_sender, _e) {
// load data in separate thread
ThreadPool.QueueWorkItem( (_state)=> {
myAdapter.Fill( myDataSet );
// refresh UI components in correct (UI) thread
frmDialog.Invoke( (Action)myDataControl.Refresh );
// close dialog
frmDialog.Invoke( (Action)frmDialog.Close() );
}
}
// shows dialog
frmDialog.ShowDialog( this );
}
Here is an example of using BackgroundWorker to do the loading of data and running a user friendly form to show 'Loading records' or similar...
public void Run()
{
bgWorkrFillDS = new BackgroundWorker();
bgWorkrFillDS.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgWorkrFillDS_RunWorkerCompleted);
bgWorkrFillDS.DoWork += new DoWorkEventHandler(bgWorkrFillDS_DoWork);
bgWorkrFillDS.RunWorkerAsync();
}
void bgWorkrFillDS_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bgWrkrFillDS = (BackgroundWorker)sender as BackgroundWorker;
if (bgWrkrFillDS != null)
{
// Load up the form that shows a 'Loading....'
// Here we fill in the DS
// someDataSetAdapter.Fill(myDataSet);
}
}
void bgWorkrFillDS_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Hide or unload the form when the work is done
}
Hope this helps...
Take care,
Tom.
I solved this problem by creating a new DataSet, loading in in the background, then calling DataSet.Merge on the UI thread. Thanks everyone for your advice, which led to this solution.
As an added bonus, this runs much faster than it used to (calling Fill in the background, which only worked with no grids open). Does anyone know why?