c# cross-thread error with UserControls - c#

Here is my situation:
I instantiated a c# usercontrol on a main thread, but it is not added to the form.
//global declaration
usercontrol1 datacontrol;
constructor()
{
.......
datacontrol = new usercontrol1();
........
}
I then have an asyhcnronous background thread that fires an event that is handled by the instantiated datacontrol. The event handler has a:
if(InvokeRequired){BeginInvoke(..);}
This should prevent any cross-threaded calls from being made. However when this gets called InvokeRequired is false so the handler is not invoked on the correct thread. So in the handler when I attemped a this.labelname.text ="blah" a cross-thread exception is thrown.
However if I add the control to a panel on the mainform, and remove it, then allow the background thread to fire the event. The handler enters but this time 'InvokeRequired' is set to true so it properly invokes itself in the mainthreads context avoiding the exception.
Can someone explain to me why the act of adding it to a panel then removing it fixes the error?
There is no onLoad events for the form so everything should be properly instantiated without it being drawn.
thanks!
stephanie

This is probably because the handle for the control has not yet been created. If you reference dataform.Handle in your constructor, it should create the handle and set the thread ID appropriately, so InvokeRequired will return true later.
You can also force the creation of a handle with CreateControl, but only if the control is visible.

When you add a Control (or Form) to a parent, it sets of the Creating of WindowHandles. Apparently it is also needed to initialize the Execution context for the InvokeRequired logic.
So, don't assume that a created but never-shown Control or Form behaves 'normally'.

Related

how to obtain Targets of an event

I have an event to fire, named ValueGenerated. The code that generates values and fires ValueGenerated is running in a thread and the method which recieves this event is on a form.control (i.e. a form). As UI thread does not allow another thread to change the UI I wrote the following code on the event generation:
if (ValueGenerated.Target is System.windows.form.control)
{
Control targetForm = ValueGenerated.Target as control;
targetForm.Invoke(ValueChanged,new object[]{this,args});
}
But I think what happens if the event is registered by more than one methode. For example, by two or three destinations. Why on the event and delegate classes we have just the Target property which returns the instance object of the last method added? Do we always need just the last one?
You're doing it wrong.
As noted in the comments, you can get the full list of invocation targets by calling GetInvocationList() on the delegate instance. Then you can invoke each target individually.
But this is not the right way to do it. Your event should treat all handlers the same.
If the event is the kind of event that is always raised in a background thread, and is always handled by a UI object, then it should always use an appropriate mechanism to dispatch to the UI thread. See the BackgroundWorker class for an example of this sort of design, specifically its ProgressChanged and RunWorkerCompleted events.
If either of those conditions are not true, then your event should not attempt to deal with the cross-thread invocation in any way. Subscribers to the event that have thread affinity should be expected to deal with that themselves.
Unfortunately, there's not enough context in your question to provide any more specific advice than that. The only thing that is clear is that you've started down a dead-end road. Turn around, come back, and take the smoother path. :)

Closing Form from inside an Invoke

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.

Is there any case in which the Form's Activated event is not raised?

I don't understand why it could be that, I thought Activated should be raised when the form is shown. In fact my form has TopLevel set to false and it's added to another form. When the main form shows, it's also visible, and I can interact with its controls but I tested and saw that the Activated is not raised.
public MainForm(){
InitializeComponent();
Form child = new Form();
child.Activated += (s,e) => {
MessageBox.Show("Activated!");
};
child.Size = new Size(200,100);
child.TopLevel = false;
child.Show();
child.Parent = this;
}
After running the MainForm the child form is appeared inside the main one and there isn't any MessageBox displayed with the message "Activated!".
What is the additional job to do to make it raise?
If the second form comes to screen for the first time, you can use Shown event.
Activate event is only fired when a form gets focus, but that does not contain showing for the first time. But, if the previous form which is active is outside of your app, it will not raise activate event. I mean it is valid when only viewing forms of same project.
Here is my answer, I noticed that only Form has Activated event, other controls don't have and once the TopLevel of Form is set to false, I think it's treated as a normal control and in that case, Activate() method will do nothing and Activated event won't be raised in any case. I think this is the reason why Activated is not raised.
Thank Kuzgun for a suggestion of using Shown instead, but this is focused on explaining why the Activated is not raised!
This answer is just my guess, even the MSDN page about Form.Activated event doesn't mention this. It should not be missed that way especially in an official documentation page.
Once the TopLevel property of Form is set to false then the form becomes a normal control, hence Activated() event will not fire.

C# WinForm doesn't close.

I have an application with one main form. In the form I have one object objC of class C. The form gets from objC my control and puts it into panel. The form interacts with objC through methods calling and subscribing to objC’s events.
When I try to close the form by clicking on [X] button or by calling this.Close(), form isn’t closing. It calls handler of FormClosing. In the handler, I call objC.Dispose(). I checked, there are no exceptions generated. In objC.Dispose() I unsubscribe from all form’s event handlers. And I removed my control from panel with this code:
splitContainerMain.Panel2.SuspendLayout();
{
splitContainerMain.Panel2.Controls.Clear();
}
splitContainerMain.Panel2.ResumeLayout();
But it just won’t close. I can try to close as many times as I will, FormClosing event will be repeated, but FormClose will be never generated.
This bug is not reproduced when I don't create a control and add it to the panel. What have I done wrong?
There are few explanations for this. But one, you might have a Validating event handler that is canceling the validation. This will also cancel OnFormClosing. Fix:
void Form1_FormClosing(object sender, FormClosingEventArgs e) {
e.Cancel = false;
}
Btw, there is no point in calling Suspend/ResumeLayout, there is no layout done at form closing time. And calling Controls.Clear() does not actually dispose the controls. Rather nasty behavior that trips up a lot of programmers. Best thing to do is to do nothing, parent controls automatically dispose their children controls. And there is no point in unsubscribing events either, the form object and the objC object only reference each other, no other references exist. The garbage collector knows how to handle that.
This question seems to be releated to : Cleaning objects off a form, Where and When?
which provides two good alternatives for dealing with your object.
Either dispose of it by implementing the Form's Dispose method or
Simply ensure that the control is added to the System.ComponentModel.IContainer components property of the form. This will ensure that it's dispose method will be called when the form is disposed.

BackgroundWorker OnWorkCompleted throws cross-thread exception

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.

Categories