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. :)
Related
I have an application written in c#. I have a class (say called ClassA) that has a simple event (code below). The event is raised to keep the user updated on the progress of the code.
public delegate void MyDelegateProgessUpdate(string value);
public event MyDelegateProgessUpdate ProgressUpdate;
When the application is running a list of ClassA objects are created (up to a maximum of 8). So all 8 objects will be raising an event (in their own class) - this works fine as I have just tested it. However I was wondering if two objects raised an event at the same time if there would be an issue?
Edit
I should have mentioned the following details. The list of ClassA objects will all be running at the same time so it is possible the events could be raised at the same time.
Task[] myTasks = new Task[numThreads];
for (int i = 0; i < myTasks.Length; i++)
myTasks [i] = Task.Factory.StartNew(ClassA[i].DoSomeWork, someData[i]);
This is all being done on a background worker thread. When an event is raised a property on my WPF UI is updated and its OnPropertyChanged event called.
The answer depends solely on the code (your code) that is executed by the events:
IF the code itself is threadsave then no there is no issue. If the code is not 100% threadsave then yes you have a possible problem at your hands (a racing condition to happen).
By itself there is no intricate problem with multiple objects raising the same event. It only depends on the code of the event itself and if the objects have the rights to raise the event.
For example if you are in your own thread and try to raise an event on the GUI which runs in its own thread, then you need to use invoke else you will have a problem
That is if you have code that raises events pretty fast (like multiple handlers set to one event, ....) where your code example doesn't go into details there that show how the events are raised (and how you raise the multiple events)
How will two objects raise events at the same time?
From the code and description you have provided, the is nothing to suggest this is actually multi-threaded code, and your events will be processed in the order that they are raised.
If you do have multiple threads, the important thing to remember is that the event handlers will be run on the thread that raised the event, so be careful if you need to modify any user interface items, and synchronize any access to resources being shared between the threads.
Edit:
I can't really comment with regards to WPF, as my experience is mostly Winforms, however this answer appears to pretty comprehensive with regards to managing cross-thread stuff in WPF.
Other than that I wouldn't expect you to have an issue of the events were raised simultaneously, provided they are not sharing a single set of data between them.
Let's say I have a user control named A that does the following:
Page_Load:
List<object> myList = PrepareList();
ListPrepared(this, new AArgs(myList)); // Event that lets subscribers modify the list.
OperateOnAndDisplayList(myList);
An instance of A is held in a page that subscribes to the ListPrepared event and modifies the list that's created. I want the page's handler to finish before I operate on and display the list, obviously, since it's modifying the list for completeness. How would I go about implementing this? (Or is this a horrible idea, since it exposes the list and thus requires outside elements to have knowledge of the list in order to modify it?)
Events are raised synchronously. This means that all subscribers to the event will execute their code before the OperateOnAndDisplayList method is called. This also means that if myList is modified by any of the event subscribers, the modification will be visible to the event publisher as well. If this isn't what you want, you should create a copy of the list before raising the event, and only send the copy with the event's EventArgs.
If the event handler is returning when all its code is done, that you don't have to worry about it. Events are just a way of invoking methods. It is not related directly to async operations.
So I am doing a somewhat lengthy progress in another class, and I want to give some progress info to my GUI. I am aware of background worker, and may use it for this if I HAVE to, but this operation is so simple that I feel that background worker is a bit more than I need. Instead, I am using eventhandlers to handle the updates, but the operation is inconsistent.
When a point has been reached in the worker class, it puts up an event telling about its progress. In the GUI class, I have an eventhandler listening for that event. When it finds it, it makes a string about it, and puts that string as the text of a label.
I then call labelname.Update() in that eventhandler, but nothing happens. Here's the really confusing part, I put a textbox there instead, set its text, and then called textboxname.Update(), and it worked. Why would .Update() not work for one control, but not another. Is there a trick to get it to work for a label?
I am turning Roken's comment into an answer, but his remark is the answer. You must implement the background operation in a background worker, period.
See this excellent article about the why and how.
(1) Try to call labelname.Invalidate(); before calling labelname.Update();
Calling the Invalidate method does not force a synchronous paint; to
force a synchronous paint, call the Update method after calling the
Invalidate method. (MSDN)
(2) Another option is calling labelname.Refresh();
Forces the control to invalidate its client area and immediately
redraw itself and any child controls. (MSDN)
that is a question I have been asking myself for a while.
Giving a certain flow of events, can I when handling one of them, stop the next ones to be raised?
For example, when collapsing a node which child was selected in a treeview (winform), the events are raised like that:
BeforeCollapse
BeforeSelect
AfterSelect
AfterCollapse
I could stop them by using a class member, but I was wondering whether there was a built-in function or just another way (a more elegant way) to achieve this, by acting directly on the events queue.
Any idea?
Not easily, no. The order of the events firing is controlled by the TreeView control class, and there is no built-in way to prevent events from firing. But you have a couple of options:
Create your own TreeView class that inherits from the base class,
then add a bool property to prevent the events from processing.
Then you can override BeforeCollapse, etc. to check the bool
before calling base.BeforeCollapse.
Just create a bool flag, and check the flag in each of the events.
No there is no way to do that for that type of event (you are asking for TreeView).
Like for example could be managed KeyEventArgs.Handled via built-in mechanism.
You can use some instance (boolean ?) value to manage the flow,
or you can, unsubscribe from the event that you don't want more recieve, but after subscribe to it again. Sounds rough solution, but sometimes turns out reasonable one.
even if the event are raised nothing will happen if you don't bind an event handler to them. In this case you can just remove the handler using the code below:
object.Event -= new EventHandlerType(your_Method)
Otherwise you should create your own custom control
according to OnBeforeCollapse you get an TreeViewCancelEventArgs which has an Cancel property. Setting this to true should stop the flow, but will also not collapse it.
Same goes for OnBeforeSelect.
The only times you can easily "cancel" an event is if the event handler has the CancelEventHandler delegate type. Even then it doesn't really cancel it as much as set a flag for the remaining events that makes it skip performing all the events subscribed to it.
If you did have a CancelEventHandler type (which these don't) you'd simply set Cancel to true on the event object itself in the handler.
Plenty of other answers give you suggestions for what you should o. I'd just go with your idea: set a 'event cancelled' flag in your control class, and check it. When the last event in the series gets called, reset it.
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.