Page 3 of 3
The short and sharp solution is to add to the form’s constructor the single line:
This just turns off checking for the problem and everything works – or at least you hope it does. In a simple situation you might be able to convince yourself that it is indeed safe but if the same property or method could be accessed due to more than one event, or worse by more than one event on a multi-processor machine, then this isn’t a sensible workaround. It also doesn't work with a WPF application.
So what is the full solution?
The Invoke method
The only reasonably safe thing to do is to use the Invoke method of the form, or whatever control the event handler belongs to, to run the event handler using the same thread as created the form – this is what the Invoke method is for.
That is, Invoke(delegate,parameters) will run the method specified in the delegate and pass it the specified parameters as an array of objects, using the correct thread of execution.
The problem here is made more difficult because the method that we want to call is specified as a delegate within the event handler and the object that it belongs to is late bound.
My first attempt at dealing with this problem produced a very complicated piece of code that used reflection to obtain the target’s Invoke method which was then called using a MethodInfo Invoke – i.e. Invoke calling Invoke - which finally called the delegate event handler – not pretty.
Then I read Late Binding - Myths and Reality and realised that I was in a “conditional late binding” situation.
The point is that the target of the delegate, i.e. the object instance that the event handler “belongs” to, either has an Invoke method or it doesn’t. In the current form of the class hierarchy everything that has an Invoke of the correct type is descended from Control.
So all we have to do is attempt a cast to Control and if successful use the Invoke method to call the delegate. If the target turns out not to have inherited from Control then we can either decide that it is threadsafe, and call the delegate without the help of invoke, or throw an exception.
One complication is that as we need to handle each event handler call individually we now have to deal with each delegate in turn. This makes the implementation of the event more complicated than then our simple first attempt
First we need a variable to cast the target object to a parameter array for the delegate call:
void Npad_Exited(object sender,
object param = new object;
param = this;
param = e;
Next we get the entire list of delegate calls:
Delegate List =Exited.GetInvocationList();
and process each one in turn:
foreach (Delegate d in List)
We attempt to cast the delegate target to Control using the as operator for simplicity.
target = d.Target as Control;
This will return a null result if the delegate target cannot be cast to a Control for any reason. We can use this to test what we should do:
if (target != null)
If the target is descended from a Control then we use the Invoke method to call the delegate using the thread that created the control. The parameters are passed as an array of objects and are used in the call to the delegate d.
If the cast has failed and target is null we simply use the DynamicInvoke method that every delegate has to allow the method it is associated with to run. Again the array of parameter objects is passed directly to the event handler.
This now all works but you can encounter similar cross-threading problems when working with explicitly multi-threaded applications. Deciding what to do in such situations is usually complex because of a basic lack of information concerning exactly how thread “unsafe” a supplied section of code is. The only thing you can do is to play safe by treating everything as a potential deadlock or race hazard.
If you would like the code for this project then register and click on CodeBin.
If you would like to be informed about new articles on I Programmer you can either follow us on Twitter, on Facebook, Digg or you can subscribe to our weekly newsletter.