Closing a form from inside an Invoke like this:
Invoke(new Action(() => {
Close();
MessageBox.Show("closed in invoke from another thread");
new Form1();
}));
throws an exception as soon as the form is closed:
Invoke or BeginInvoke cannot be called on a control until the window
handle has been created.
But only on NET 4.0. On NET 4.5 no exceptions are thrown.
Is this expected behavior? How should I go about it?
That's because Close method closes the form and destroys it's handle and then the MessageBox is invoked in the Closed form with no handle, so the error message shows up.
I don't understand your purpose, but you should either move the code after Close out of invoke, or move the Close after them. For example:
Invoke(new Action(() => {
Hide();
MessageBox.Show("closed in invoke from another thread");
new Form1();
Close();
}));
Edit:
MSDN note about Control.Invoke:
The Invoke method searches up the control's parent chain until it finds a control or form that has a window handle if the current control's underlying window handle does not exist yet. If no appropriate handle can be found, the Invoke method will throw an exception. Exceptions that are raised during the call will be propagated back to the caller.
If you start a thread during initialisation, you do not know how far the initialisation has gone in another thread.
You notice differences in behavior on different .Net versions, but you cannot be sure about the order of things on different machines.
I have solved a lot of threading issues in Windows forms using my own messagepump, using a Queue and a normal Timer control:
Add a timer control to your form, with a small interval (250 ms)
Add a Queue to your form.
Let the timer event dequeue the actions, and execute it.
Add Actions to the queue during initialisation or even other background jobs.
Using this approach will issues with background jobs during initialisation, but also during closing/disposing of the form, since the timer will only trigger if the form is fully functional.
Related
I try to show a MESSAGE to the user while an operation is executed. The MESSAGE won't show any button. Just a MESSAGE (text) and maybe a background image.
The problem is the following:
MessageBox does not seem to be the good control (because of button and it blocks the running process).
Form.ShowDialog() also blocks the running process. I don't know what to do.
I want to show the message, run my process, and dispose the message when the process is done.
How to achieve this in C# ?
Create a simple form with the message (or expose a public property to be able to change the message, or a constructor with message parameter to pass it in) and show the form using this Show overload. Then disable the (entire) original (owner) form (or just disable the controls you don't want accesible).
So, in your "main" form do this:
Form f = new MessageForm();
f.Show(this); //Make sure we're the owner
this.Enabled = false; //Disable ourselves
//Do processing here
this.Enabled = true; //We're done, enable ourselves
f.Close(); //Dispose message form
Also, consider using a BackgroundWorker.
create custom form, and write own behavior
Create a custom form set it to minimal styles etc. Make sure it knows when the process is complete, say by passing in a reference to the process and checking it every now and then.
One way you could do it is create a worker class that raises an event when it is finished.
execute the worker class in a new thead so it runs it the backgroud.
create the modal form with a method to close it as the event handler for the "finished" event.
This way the ui still works and responds and the form will close once the worker thread is finished.
I have a UI design question.
I would like to exit the application when it encounter an exception on a non-UI thread.
Basically, the event goes like this:
Main Form -> ShowDialog of sub WinForm (MainThread)-> Starts a background thread (WorkerThread) -> Exception occurs -> Show an ErrorForm (WorkerThread)
When the user click Exit button on the ErrorForm, i want to exit the entire application. However, doing the following call doesn't work.
Invoker.Invoke((Action)(() => { Application.Exit(); }), null);
The Invoker reference to the main form SynchronizedContext. However, since the MainThread is still waiting for the subWinForm to return its control, it probably can't handle the Application.Exit().
What would be a better design to handle exception that is thrown by a background worker thread?
Cancel the background worker and send an argument to the BackgroundWorker RunWorkerCompletedEvent to identify there is an exception. After that call the Application.Exit() from there would be fine.
I know that invoking like this works in Silverlight:
Dispatcher.BeginInvoke(() => Application.Exit());
Or if there's no Dispatcher for your WinForms classes:
Invoker.BeginInvoke(() => Application.Exit());
You had a lot of extra unnecessary code ((Action), null, unnecessary brackets and parentheses). I don't think it would have stopped it from working correctly, but in any case, it's easier to read like this.
I am displaying dialog on the main form, now one for loop is working behind but when that dialog will displayed code execution will be stopped, but I don't want to let stop the execution of the code, is any other any way to do that ?
EDIT: I am using now thread to do that and my code is like
Thread t;
private void StartParsingByLoop()
{
t = new Thread(new ThreadStart(RunProgress));
t.Start();
for (int i = 0; i < dialog.FileNames.Length; i++)
{
cNoteToParsed.AllContrctNotesFilePath.Add(dialog.FileNames[i].ToString());
}
cNoteToParsed.ParseContractNote();
if (cNoteToParsed.NOfContrctNoteParsed > 0)
LoadTransactionsInDataGridView();
t.Abort();
}
private void RunProgress()
{
frmProgress progressForImportingTran = new frmProgress("Importing Transactions", "ok");
progressForImportingTran.ShowDialog();
}
Now I have problem is that the dialog that shows the progress does not behave like dialog and gives access of the main form and if we try to access the main form then dialog goes to hide. And I dont want to make the dialog be hide.
You can let a different thread handle the loop.
Response to edit: Can you provide more details, perhaps some code? what is the loop doing? what form do you display?
(this answer is based on the assumption that we are taking about a winforms app)
Show the form using the Show method, rather than ShowDialog. By passing a reference to the main form, the dialog will stay on top of the main form even if it is not modal:
TheDialog dialog = new TheDialog();
dialog.Show(this);
Note though that the user can still interact with the controls on the main form, so you might want to disable some controls, depending on your scenario.
You state in your question that there are requirements that prevent you from using threading for this. This kind of requirement strikes me as odd, and it is a pity because this is one of the typical scenarios when you would want to use some sort of asynchronous construct. By performing heavy work on the UI thread, you get some drawbacks, including:
The UI will not be responsive - if you want to allow the user to cancel the work by clicking a button, that will be tricky to achieve in a robust manner.
The UI will not redraw properly since the UI thread is busy performing other work.
Do not use ShowDialog() but Show() to display the dialog windows. Show() will immediately return to the caller whereas ShowDialog() will hold the execution. Note that when using Show() your dialogs won't be modal anymore.
OK, so you don't need modal dialog, you need a mechanism for your user not to be able to select your main form while processing is enabled.
Modal dialog doesn't mean that execution is stopped - it just happens somewhere else.
There are several methods to do this, and you ruled out new thread creation (don't know why, it would solve it elegantly). If don't use thread, you'll have another problem - your processing should be done in CHUNKS and every little while you'll have to do something like Application.DoEvents() to enable your application to process messages and not be frozen to the user.
So, if you can create a method in the main form that shows your 'please wait' dialog which will perform some work and later do some more until is finished, you can do this:
create a new form (wait dialog)
start a timer inside of it and wire timer to the PARENT form
timer interval should be 1
ShowDialog() the form
on timer event do small amount of work (don't allow it to go more then 1/10 of seconds)
Can you do that?
Anyway:
task can't be split into small workable pieces
you can't use threads
you want your UI to be responsive
PICK 2. You can't have all 3 in Winforms.
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.
I want to call ShowDialog() when a keyboard hook event is triggered, but I'm having some difficulties:
ShowDialog() blocks, so I can't call it from the hook triggered event, because it will block the OS.
I can start a new thread and call ShowDialog() from there, but I get some nasty exception. I guess I can't call ShowDialog() in any other thread.
I can start a timer: in the next 50 milliseconds call ShowDialog() (which is a nasty hack BTW, and I rather not do this). But then the timer fires in a new thread, and then I run into the same problem explained in the previous bullet.
Is there a way?
The problem may be that you are trying to put UI in a non-UI thread. Make your event fire from another thread and invoke the method that runs ShowDialog() from your UI thread.
Essentially, you want to keep your UI on the UI thread and move anything else to a back ground thread.
Check out Gekki Software for some details (there are zillions of others - this just happens to be the first one I found in research archives).
I'm not sure about ShowDialog, but whenever you get an exception when trying to do something with the UI in a background thread, it means you should use the UI dispatcher.
Try calling the BeginInvoke method (if you are on Windows Forms) of any UI object you control with a delegate that calls the showdialog.
Also, make sure to try (before this) passing a reference to a valid owner in the show dialog method.
Try this:
void MyKeyboardHookHandler(...)
{
WindowsFormsSynchronizationContext.Current.Post(state =>
{
Form f = new Form();
f.ShowDialog();
}, null);
}
You really should be able to show the dialog from a KeyPress type event.
Also, if you use ShowDialog() from another thread, it will not be modal (no parent). It would be the same as using Show().
Without the "nasty exception" it's hard to tell what's going on. I would assume it's because your thread isn't an STA thread, and the UI objects are throwing the exception when they get instantiated. Set your new thread's apartment model to be STA instead of MTA and see if that helps.
And if you don't know what the difference is, you should do some reading up, for instance Multithreaded Apartments (MSDN).
ShowDialog() will block your application's thread, but that's what it's supposed to do. If you don't want the form blocking your application, call Show() instead.
ShowDialog() will not "block the OS", so don't be reluctant to use it.