|
|
Thanks for the awesome code!
|
|
|
|
|
I think reflection is a bit slow, but still a nicely written article.
Just because the code works, it doesn't mean that it is good code.
|
|
|
|
|
|
Yes, maybe in the most scenarios the backgroundWorker from the .net framework is the best solution for cross thread operation involving the UI. But I use the solution prensented in the article to show a reaction in the UI from a EventHandler that reacts to a change in somewhere else running code, allowing me to write code like:
Private Sub BusDriver_TelegramReceived(sender as Object, e as TelegramEventArgs)<br />
If SomeCheck(e.Content) then<br />
Me.myThreadSafePropertySetter(Of String)(Me.Label1, "Text", _<br />
String.Format("Received: {0}",DateTime.Now))<br />
Dim col as System.Drawing.Color = Me.FindColor(e.Content)<br />
Me.myThreadSafePropertySetter(Of Color)(Me.Button1, "ForeColor", col)<br />
End If<br />
End Sub
instead of:
Delegate Sub SetControlTextDelegate(ctrl as Control, text as String)<br />
Private Sub SetControlText(ctrl as Control, text as String)<br />
If Me.InvokeRequired then<br />
Me.Invoke(new SetControlTextDelegate(addressof SetControlText), new Object(){ctrl, text})<br />
return<br />
End If<br />
ctrl.Text = text<br />
End Sub<br />
Delegate Sub SetControlColorDelegate(ctrl as Control, col as Color)<br />
Private Sub SetControlText(ctrl as Control, col as Color)<br />
If Me.InvokeRequired then<br />
Me.Invoke(new SetControlColorDelegate(addressof SetControlColor), new Object(){ctrl, col})<br />
return<br />
End If<br />
ctrl.BackColor = col<br />
End Sub<br />
Private Sub BusDriver_TelegramReceived(sender as Object, e as TelegramEventArgs)<br />
If SomeCheck(e.Content)<br />
Dim col as System.Drawing.Color = Me.FindColor(e.Content)<br />
Me.SetControlText(Me.Label1, String.Format("Received: {0}",DateTime.Now))<br />
Me.SetControlColor(Me.Button1, col)<br />
End Sub
although more efficient, I consider the second solution very annoying to write, and, worst of all, error prone because of the repetitiveness.
bye
sr
|
|
|
|
|
You could greatly increase the performance by caching the PropertyInfo instances.
Also, I compared your approach to LCG and generic delegates - for direct invocation LCG seems to be the most performant followed closely by generic delegates. Reflection is much slower than any of the alternatives. Of course, direct call is substantially faster than any other approach. Using ISynchronizeInvoke.Invoke seems to level the playing field between LCG, generic delegates and reflection.
ISynchronizeInvoke.Invoke - 10K calls, average of 100 tests
662 Generic Delegate
624 LCG
685 Reflection
Direct invocation - 10K calls, average of 100 tests
0 Direct Call (too small to measure)
10 Generic Delegate
8 LCG
66 Reflection
So, it seems if you want a balance between flexibility, usability and performance (or if you just don't like MethodInfo.Invoke) you'd probably want to use LCG.
Public Class PropertySetter
Private Delegate Sub DoSetPropertyDelegate(ByVal o As ISynchronizeInvoke, ByVal p As String, ByVal v As Object)
Private Delegate Sub DynamicMethodDelegate(ByVal target As Object, ByVal value As Object)
Private Delegate Sub SetPropertyInvoker(Of T)(ByVal value As T)
Private Shared DelegateProperties As New Dictionary(Of String, [Delegate])
Private Shared LCGProperties As New Dictionary(Of String, DynamicMethodDelegate)
Private Shared ReflectionProperties As New Dictionary(Of String, MethodInfo)
Private Shared DoSetPropertyReflection As New DoSetPropertyDelegate(AddressOf SetPropertyReflection)
Public Shared Sub SetPropertyDelegate(Of T)(ByVal o As ISynchronizeInvoke, ByVal p As String, ByVal v As T)
Dim name As String = DirectCast(o, Object).GetHashCode().ToString() & p
Dim d As [Delegate]
If DelegateProperties.ContainsKey(name) Then
d = DelegateProperties(name)
Else
d = [Delegate].CreateDelegate(GetType(SetPropertyInvoker(Of T)), o, "set_" & p)
'Cache the delegate for performance comparison ONLY
' *** Note that caching like this will keep the object alive ***
' *** until the reference to the delegate is removed ***
DelegateProperties(name) = d
End If
If o.InvokeRequired Then
o.Invoke(d, New Object() {v})
Else
DirectCast(d, SetPropertyInvoker(Of T)).Invoke(v)
End If
End Sub
Public Shared Sub SetPropertyLCG(ByVal o As ISynchronizeInvoke, ByVal p As String, ByVal v As Object)
Dim t As Type = DirectCast(o, Object).GetType()
Dim methodName As String = "Set_" & t.FullName & "_" & p
Dim d As DynamicMethodDelegate
If LCGProperties.ContainsKey(methodName) Then
d = LCGProperties(methodName)
Else
Dim dm As New DynamicMethod("", GetType(Void), New Type() {GetType(Object), GetType(Object)}, GetType(PropertySetter))
Dim cg As System.Reflection.Emit.ILGenerator = dm.GetILGenerator()
Dim mi As MethodInfo = t.GetProperty(p).GetSetMethod()
If Not mi.IsStatic Then
cg.Emit(OpCodes.Ldarg_0)
End If
cg.Emit(OpCodes.Ldarg_1)
If mi.IsFinal OrElse Not mi.IsVirtual Then
cg.Emit(OpCodes.Call, mi)
Else
cg.Emit(OpCodes.Callvirt, mi)
End If
cg.Emit(OpCodes.Ret)
d = DirectCast(dm.CreateDelegate(GetType(DynamicMethodDelegate)), DynamicMethodDelegate)
LCGProperties(methodName) = d
End If
If o.InvokeRequired Then
o.Invoke(d, New Object() {o, v})
Else
d.Invoke(o, v)
End If
End Sub
Public Shared Sub SetPropertyReflection(ByVal o As ISynchronizeInvoke, ByVal p As String, ByVal v As Object)
If o.InvokeRequired Then
o.Invoke(DoSetPropertyReflection, New Object() {o, p, v})
Return
End If
Dim t As Type = DirectCast(o, Object).GetType()
Dim propName As String = "Set_" & t.FullName & "_p"
Dim pi As MethodInfo
If ReflectionProperties.ContainsKey(propName) Then
pi = ReflectionProperties(propName)
Else
pi = t.GetProperty(p).GetSetMethod()
ReflectionProperties(propName) = pi
End If
pi.Invoke(o, New Object() {v})
End Sub
End Class
-- modified at 10:45 Wednesday 27th December, 2006
Know before Whom you are standing! - Berakhot 28b
|
|
|
|
|
What units are those times in, its meaningless without the units.
If its ms then sometimes (most times) in the real world 600ms for 10,000 calls is perfectly acceptable, considering the process that generates the 10,000 calls or could take a few minutes or even hours i don't think a user is going to bitch that it takes 0.6 seconds too long.
Most often the things i invoke using ISyncInvoke are done once or at most a couple of hundred times, which would take between 0.0665 (for 1) and 1.3 (for 500) of your units, which depending on the size of your units, is completely unnoticeable by humans.
|
|
|
|
|
Wow, good piece of code: you made me courious about LCG and the System.Reflection.Emit namespace, I'll try it and, perhaps, update the article considering the alternatives you suggest.
Sure, when direct invocation is possible there's no doubt, we have to use it, but when we have to use the ISynchronizeInvoke.Invoke method the three alternatives seems to perform the same (at least so would appear to a human user), this because the loss of performance is mostly due to the thread context change (which we cannot avoid) than to the use of reflection.
Caching the propertyInfo instances is (imho) the best idea here.
|
|
|
|
|
Firstly, this approach is a very elegant and simple one that satisfies many needs, albeit it may have performance penalties in a heavy-use environment. Thank you to the author.
However, I must comment on another comment made: In attempting to do a similar thing and continually seeing people saying to check 'Invoke Required', I've not been able to successfully check 'invoke required' except on the thread that created the object. From other classes and/threads, I just don't know how to get addressability to the control that needs to be checked. Can someone explain to me why everyone says this is so easy and I haven't yet figured it out?
Thanks, Tom
|
|
|
|
|
Please provide a sample code to work on, in order to reply to your question. thanks
If knowledge can create problems, it is not through ignorance that we can solve them. (Isaac Asimov)
|
|
|
|
|
Hi Filip,
I had concocted a long explanation of my problem and lost it when I went to preview the message, and I'm too lazy to do it again. So bear with me.
The application is essentially two pieces, a Windows-FormsClass (main thread) and many instances of a a Network-InputHandlerClass and a thread for each instance. The InputHandler desires to update the text property of a Control in the Windows-FormsClass. The Network-InputHandlerClasses are instantiated and threads started by the main Windows-FormsClass.
I've circumvented the entire issue of 'invokerequired' by using a pseudo-bind facility through events, but I can't help but feeling that others have used this seemingly-more elegant 'invokerequired' facility, but for the life of me, I can't see how to use it.
As I said before, my problem is getting 'addressability' of some sort to the Control(.text) in the Windows-FormsClass (thread) from one of the Network-InputHandlerClass threads to be able to actually make the test of the 'invokerequired' property. In other words, the code:
if( X.InvokeRequired) // this won't necessarily compile; it shows intent
{
Invoke();
}
can't find a suitable way to get to the runtime instance of X.
Thanks for any insights you might offer.
Tom
|
|
|
|
|
It seems like you put the code:
if( X.InvokeRequired)
{
Invoke();
}
in the Network-InputHandlerClass itself: if so, your business objects knows the GUI, and that's not a best practice. You should use the following pattern instead:
- Declare a public event in the NetworkInputHandlerClass
- the Windows-FormsClass (which have access to both the Network-InputHandlerClass and the GUI elements you want to manipulate) declare an event-handler and subscribe to the events of the NIHC it instantiates
- in the event handler put the invokeRequired check
This way, your NIHC classes don't know any GUI and will work whatever way you present their computation to the user (e.g. they will work also in a console app) and you execute the InvokeRequired check in the right place, i.e. in the GUI class.
i hope this will help
If knowledge can create problems, it is not through ignorance that we can solve them. (Isaac Asimov)
|
|
|
|
|
Thanks, Siroman.
I hadn't considered doing it that way, but now I will go look at my code and see what sort of impact this would have .. it does seem like a better method, particularly because of the isolation it offers between the classes.
I'll let you know what my code inspection reveals.
Thanks, Tom
|
|
|
|
|
I'm not so sure what you mean by "not being able to check it from other threads". You are allowed to check control.InvokeRequired from any thread, it's thread-safe (otherwise it would be of little use).
If the problem is you can't get to your UI classes, the real question is, why are you trying to check InvokeRequired in classes that seemingly have nothing to do with your UI?
|
|
|
|
|
That's exactly why I think I must be missing something important about how to use it!
tom
|
|
|
|
|
Sometimes you don't have access to an object that implements ISyncInvoke, eg your already in a class that doesn't take in a control as a parameter but you want easy synchronization, in .Net2 you can create a SynchronizationContext which acts on the thread it was created on, so you could provide an overloaded constructor that is no object is passed in then it can use that instead. I have used it when my object couldn't get a ISyncInvoke object.
My approach is sometimes different (don't get me wrong i think yours is really neat btw, i may end up using it at some point, and appears very similar to how DataBinding works as far as reflection goes though they might be able to cache the reflection objects in the databindings object, you just take that approach a bit further, so good work on that) i invoked the events on the callers thread instead of just using RaiseEvent (if they had subscribers to that event of course to save pointless invoking, by checking the hidden variable eg for the Progress event i would check ProgressEvent is nothing) which allowed me to do lots of work without multiple invokes.
Keep up the good work, its such a pity that we have to do this stuff ourselves it should be in the framework.
|
|
|
|
|
OR you could provide clean seperation between UI-code and non-UI-code. Just saying.
|
|
|
|
|
Which is why i raise events which does separate UI and not UI code, if you want to update progress on a long job then its really the only way and so why not raise the event on the thread that actually wants it.
|
|
|
|
|
It's not necessary to use a class which takes a ISynchronizeInvoke instance in the constructor, you may consider to use an utility static class (e.g. see the code posted in the comment titled "Performance") and pass to its static methods the ISyncInvoke obj. Sure, neither an instance class nor a static one could be use in your approach, because you want directly raise the event in the correct thread context, instead of forcing the event handler to wonder if it's executing in the UI creation thread: very elegant. But you have always to provide the SynchronizationContext to the event raiser, isn't it?
thanks
|
|
|
|
|
Why would you want to access the control via another thread then the one that created it ? This will only cause problems and that is why this exception was finally introduced in the .Net framework 2.0.
|
|
|
|
|
First, I don't think we have to avoid cross-thread calls at all: if so the ISynchronizeInvoke interface would have no sense, if it exists, we can use it.
About possible scenarios, think a thread in your application continously polls, e.g., the serial port, communicating with a device (in our real world application we use it to interface with a "domotic" device network) and the controls in your forms represent the status of the device. If such a status changes the piece of code polling the serial port fires an event, whose handler is executed in the thread of the polling code: the handler have to change, e.g. the text of a label wich indicates if a lamp is on or off... In general I think we could apply the approach described in the article whenever the GUI represents the status of an object that changes in another thread.
-- modified at 9:51 Tuesday 19th December, 2006
If knowledge can create problems, it is not through ignorance that we can solve them. (Isaac Asimov)
|
|
|
|
|
Juicy wrote: Why would you want to access the control via another thread then the one that created it ? This will only cause problems and that is why this exception was finally introduced in the .Net framework 2.0.
You shouldn't _directly_ access the control from another thread, indeed. But the most common solution for that problem is to wrap whatever you want to do with the control inside a delegate and have that delegate invoked on the thread that owns the controls. This is exactly what he's doing here.
That being said, I'm still wondering "Why". Usually you'll want to do more than just change one property value of a control (or do other things that change a property value), both of which are better done using other solutions. Using this code to change two property values consecutively will result in a serious performance hit because of the reflection and thread context switching.
|
|
|
|
|
Filip Duyck wrote: That being said, I'm still wondering "Why". Usually you'll want to do more than just change one property value of a control (or do other things that change a property value), both of which are better done using other solutions. Using this code to change two property values consecutively will result in a serious performance hit because of the reflection and thread context switching.
The purpose of the article is to provide a simple way to access a control property from a thread different than that on which the control itself was created, no more, no less. Such a topic is well documented in the msdn library, at the page http://msdn2.microsoft.com/en-us/library/ms171728.aspx[^].
The article approach simplifies the creation of a specific delegate for whatever property you want to change, simply there's no need for create such delegates.
Filip Duyck wrote: both of which are better done using other solutions
Any suggestion about better solutions are welcome, thank you.
If knowledge can create problems, it is not through ignorance that we can solve them. (Isaac Asimov)
|
|
|
|
|
To achieve something similar to:
Me.ThreadSafePropertySetter1.SetCtrlProperty(Of Color)(Me.button1, "BackColor", Color.Aqua)
You could just write:
if (InvokeRequired)<br />
Invoke(new MethodInvoker(delegate() { button1.BackColor = Color.Aqua; });<br />
else<br />
button1.BackColor = Color.Aqua;
Or if you're lazy by nature:
public class InvocationHelper<br />
{<br />
public static void Invoke(ISynchroniseInvoke invoker, Delegate d)<br />
{<br />
if (invoker.InvokeRequired)<br />
invoker.Invoke(new MethodInvoker(d));<br />
else<br />
d.DynamicInvoke();<br />
}<br />
}<br />
<br />
...<br />
<br />
InvocationHelper.Invoke(delegate() { button1.BackColor = Color.Aqua; }
The obvious advantages are no reflection needed and the possibility to execute more commands at once.
|
|
|
|
|
For those of us who use Vb.net, there's no anonymous methods, so we can't use them (at least, so I know). The only way I see to apply your approach in Vb.net is to declare a parameterless sub (void function with no parameters) and pass it to the ctor of the MethodInvoker object. Of course I can execute more commands at once in such a way, and there's no need for reflection, but we have to declare a different method for whatever control property we want to change and whatever control we want to manipulate. Further, I see no way to pass to such methods some parameters, so I'm forced to write a method that set the BackColor property to Color.Aqua and another method to set the same property to Color.Blue!
I want just to avoid those method declarations at all, even having to pay the performance decreasing due to reflection (when there's no particular performance needs, of course).
If you know a simpler way (eventually similar to the C# anonymous methods approach) that works also in Vb.Net and avoids reflection, please let me know about it.
If knowledge can create problems, it is not through ignorance that we can solve them. (Isaac Asimov)
|
|
|
|
|