-
Apr 6th, 2011, 04:41 PM
#1
[Extension] Set control properties across threads
[C# version here]
Updated: includes methods and functions now as well.
Update:
ForumAccount suggested that the code can be used for any type that implements ISynchronizeInvoke, not just Controls, see here. Since I don't have time to update all this code right now, if you need this change you can just change every Control reference to ISynchronizeInvoke instead.
-------
Hi,
Usually if you want to set a property of a control from another thread you will get an invalid cross-thread operation exception. To solve this you need to invoke a delegate on the same thread, which usually goes like this:
vb.net Code:
Private Delegate Sub SetLabelTextDelegate(ByVal text As String) Private Sub SetLabelText(ByVal text As String) If Label1.InvokeRequired Then Label1.Invoke(New SetLabelTextDelegate(AddressOf SetLabelText), text) Else Label1.Text = text End If End Sub
This works fine, but the problem is that it's kind of 'wordy' and can get really annoying to type if you need it for many properties and controls.
I thought I could come up with a quicker solution and I managed to create two extension methods (on Control) to get and set a property safely across threads:
vb.net Code:
Option Strict On Option Infer On Imports System.Linq.Expressions Imports System.Reflection Imports System.Runtime.CompilerServices Module Extensions Private Delegate Sub SetThreadSafePropertyDelegate(Of T)(ByVal control As Control, ByVal [property] As Expression(Of Func(Of T)), ByVal value As T) Private Delegate Function GetThreadSaferopertyDelegate(Of T)(ByVal control As Control, ByVal [property] As Expression(Of Func(Of T))) As T Private Delegate Sub InvokeThreadSafeMethodDelegate(ByVal control As Control, ByVal method As Expression(Of Action)) Private Delegate Function InvokeThreadSafeFunctionDelegate(Of T)(ByVal control As Control, ByVal [function] As Expression(Of Func(Of T))) As T ''' <summary> ''' Sets the specified property of this control to the specified value safely across threads by invoking a delegate if necessary. ''' </summary> ''' <typeparam name="T">The type of the property. Can usually be inferred from usage.</typeparam> ''' <param name="control">The control to set the property on.</param> ''' <param name="property">The property to set as a lambda expression.</param> ''' <param name="value">The new value of the property.</param> <Extension()> _ Public Sub SetThreadSafeProperty(Of T)(ByVal control As Control, ByVal [property] As Expression(Of Func(Of T)), ByVal value As T) If (control.InvokeRequired) Then Dim del = New SetThreadSafePropertyDelegate(Of T)(AddressOf SetThreadSafeProperty) control.Invoke(del, control, [property], value) Else Dim propertyInfo = GetPropertyInfo([property]) If (propertyInfo IsNot Nothing) Then propertyInfo.SetValue(control, value, Nothing) End If End If End Sub ''' <summary> ''' Gets the value of the specified property of this control safely across threads by invoking a delegate if necessary. ''' </summary> ''' <typeparam name="T">The type of the property. Can usually be inferred from usage.</typeparam> ''' <param name="control">The control to get the property from.</param> ''' <param name="property">The property to get the value from as a lambda expression.</param> ''' <returns>The value of the specified property.</returns> <Extension()> _ Public Function GetThreadSafeProperty(Of T)(ByVal control As Control, ByVal [property] As Expression(Of Func(Of T))) As T If (control.InvokeRequired) Then Dim del = New GetThreadSaferopertyDelegate(Of T)(AddressOf GetThreadSafeProperty) Return DirectCast(control.Invoke(del, control, [property]), T) Else Dim propertyInfo = GetPropertyInfo([property]) If (propertyInfo IsNot Nothing) Then Return DirectCast(propertyInfo.GetValue(control, Nothing), T) End If End If Return Nothing End Function ''' <summary> ''' Invokes a method of this control safely across threads by invoking a delegate if necessary. ''' </summary> ''' <param name="control">The control to invoke the method on.</param> ''' <param name="method">The method to invoke as an expression.</param> <Extension()> _ Public Sub InvokeThreadSafeMethod(ByVal control As Control, ByVal method As Expression(Of Action)) If (control.InvokeRequired) Then Dim del = New InvokeThreadSafeMethodDelegate(AddressOf InvokeThreadSafeMethod) control.Invoke(del, control, method) Else method.Compile().DynamicInvoke() End If End Sub ''' <summary> ''' Invokes a function of this control safely across threads by invoking a delegate if necessary. ''' </summary> ''' <typeparam name="T">The return type of the function to invoke. Can usually be inferred from usage.</typeparam> ''' <param name="control">The control to invoke the function on.</param> ''' <param name="function">The function to invoke as an expression.</param> ''' <returns>The result of the function to invoke.</returns> <Extension()> _ Public Function InvokeThreadSafeFunction(Of T)(ByVal control As Control, ByVal [function] As Expression(Of Func(Of T))) As T If (control.InvokeRequired) Then Dim del = New InvokeThreadSafeFunctionDelegate(Of T)(AddressOf InvokeThreadSafeFunction) Return DirectCast(control.Invoke(del, control, [function]), T) Else Return DirectCast([function].Compile().DynamicInvoke(), T) End If End Function Private Function GetMemberInfo(ByVal expression As Expression) As MemberInfo Dim memberExpression As MemberExpression Dim lambda = DirectCast(expression, LambdaExpression) If (TypeOf lambda.Body Is UnaryExpression) Then memberExpression = DirectCast(DirectCast(lambda.Body, UnaryExpression).Operand, MemberExpression) Else memberExpression = DirectCast(lambda.Body, MemberExpression) End If Return memberExpression.Member End Function Private Function GetPropertyInfo(ByVal expression As Expression) As PropertyInfo Dim memberInfo = GetMemberInfo(expression) If (memberInfo.MemberType = Reflection.MemberTypes.Property) Then Return DirectCast(memberInfo, PropertyInfo) End If Return Nothing End Function End Module
It looks complicated but usage is pretty easy. You'll need to pass the property as a lambda expression (which turns out something like "Function() <property here>"), as well as the value you want to set it to. Even though the extension method takes a type parameter, it can usually be inferred from the objects you pass (the property lambda and the value) so you won't need to supply it.
Usage examples:
vb.net Code:
' Set label text Label1.SetThreadSafeProperty(Function() Label1.Text, "test text") ' Disable button Button1.SetThreadSafeProperty(Function() Button1.Enabled, false) ' Get textbox text Dim text As String = TextBox1.GetThreadSafeProperty(Function() TextBox1.Text) ' Invoke some method (note: Sub() instead of Function() !) CustomControl1.InvokeThreadSafeMethod(Sub() CustomControl1.SomeMethod("a", 3)) ' Invoke some function Dim x = CustomControl2.InvokeThreadSafeFunction(Function() CustomControl2.SomeFunction(y))
I'm not that much into threading yet, but it seems to work fine for a few little tests I've done.
The only drawback is that it uses Reflection which can be a little slow, but it's only one call and I think it should be fine.
I'm open to suggestions! One thing I don't much like is that it seems a little redundant to use the control name twice (first to call the extension method on, second to pass the property you want to set), but the only way I can think of to get around that is to pass the property name as a string, and I don't like that approach at all as it's error prone (no compile errors if you miss-spell it)...
Enjoy.
Last edited by NickThissen; Jul 9th, 2011 at 03:25 PM.
-
Apr 7th, 2011, 12:25 AM
#2
Re: [Extension] Set control properties across threads
Looks good.
Originally Posted by NickThissen
I am actually trying to figure out how to do invoke a method (or function) this way, but I cannot figure out how to pass a method to the extension method without the user having to declare a delegate (which is exactly what I'm trying to avoid), so that's on hold for the moment.
I think that you would just have to overload your extensions using Func(Of TResult), Func(Of T, TResult), Func(Of T1, T2, TResult), Action, Action(Of T), Action(Of T1, T2), etc. In .NET 4.0, Action and Func delegates support up to 16 arguments. I think that it's 4 in .NET 3.5.
-
Apr 11th, 2011, 08:24 PM
#3
Re: [Extension] Set control properties across threads
Thanks, I finally got around to trying that and it does seem to work. However, is there no other way, some way to make it accept any parameters (in the worst case: just as an object parameter array)? I don't fancy writing out the code for 16 parameters for both methods and functions...
Edit:
I seem to have found a way, I can pass the method as an Expression<Action>, cast its Body to MethodCallExpression and then Invoke its Method property;
vb.net Code:
Dim methodExpression = DirectCast(expression.Body, MethodCallExpression) methodExpression.Method.Invoke(control, args)
This seems to work for an arbitrary amount of parameters but it does require me to pass any parameters twice: once to the method itself (I suppose to support different overloads) and then to my extension method call as well:
vb.net Code:
Dim r = New Rectangle(1, 2, 3, 4) Label1.InvokeThreadSafeMethod(Sub() Label1.Invalidate(r), r)
That's not really ideal either... Furthermore, it is entirely valid to compile code where you pass an argument to the method expression and not to the extension or vice versa:
vb.net Code:
Dim r = New Rectangle(1, 2, 3, 4) Label1.InvokeThreadSafeMethod(Sub() Label1.Invalidate(), r) 'forgot 'r' in Label1.Invalidate(r) Label1.InvokeThreadSafeMethod(Sub() Label1.Invalidate(r)) 'forgot to pass 'r' as argument
Both compile just fine but throw exceptions during run-time...
Last edited by NickThissen; Apr 11th, 2011 at 09:08 PM.
-
Apr 11th, 2011, 08:48 PM
#4
Re: [Extension] Set control properties across threads
Originally Posted by NickThissen
Thanks, I finally got around to trying that and it does seem to work. However, is there no other way, some way to make it accept any parameters (in the worst case: just as an object parameter array)? I don't fancy writing out the code for 16 parameters for both methods and functions...
You'd have to use Reflection again and I think it would be more complex this time. That doesn't mean that you shouldn't do it though.
-
Apr 11th, 2011, 09:23 PM
#5
Re: [Extension] Set control properties across threads
Originally Posted by jmcilhinney
You'd have to use Reflection again and I think it would be more complex this time. That doesn't mean that you shouldn't do it though.
I found a different way but it has some major drawbacks, see my edited post in case you didn't already. In the meantime I'll try to look for a better way using Reflection.
-
Apr 11th, 2011, 09:36 PM
#6
Re: [Extension] Set control properties across threads
I don't think it's possible... As far as I can find, it is impossible to pass a method without providing the parameters, as that would mean passing a 'method group' (group of overloads) which seems impossible from a short google search.
I am now looking for a way to invoke the method without having a list of parameter values, which doesn't seem possible either. I'd think the Expression<Action> would somehow be able to give me the list of parameter values, but I cannot find how. I can get the MethodInfo, and from that I can get an array of ParameterInfo objects, but those cannot give me the value of the parameters, just the names and types, so that's useless too...
In short, I can do this:
vb.net Code:
Public Sub SomeMethod(ByVal control As Control, ByVal method As Expression(Of Action)) Dim methodInfo = DirectCast(method.Body, MethodCallExpression).Method methodInfo.Invoke(control, args...?) End Sub 'Call: Label1.SomeMethod(Sub() Label1.Invalidate(rect))
This way I'm passing the 'rect' argument just once, but there is no way to get its value in the SomeMethod extension... So I cannot invoke the method. The only way I can find is what I already had: pass the arguments into the SomeMethod method separately, but then I'm passing them twice...
Any idea's?
-
Apr 11th, 2011, 09:41 PM
#7
Re: [Extension] Set control properties across threads
Given that Control.Invoke doesn't guarantee type safety, I guess you could just follow it:
vb.net Code:
Imports System.Runtime.CompilerServices Public Module ControlExtensions <Extension()> Public Function InvokeThreadSafeMethod(sender As Control, method As [Delegate]) As Object Return sender.InvokeThreadSafeMethod(method, Nothing) End Function <Extension()> Public Function InvokeThreadSafeMethod(sender As Control, method As [Delegate], ParamArray args As Object()) As Object If sender.InvokeRequired Then Return sender.Invoke(method, args) Else Return method.DynamicInvoke(args) End If End Function End Module
You would then call that InvokeThreadSafeMethod in exactly the same way as you would usually call Invoke. It will accept any delegate and any number of arguments of any type.
-
Apr 11th, 2011, 09:50 PM
#8
Re: [Extension] Set control properties across threads
Example usage:
vb.net Code:
Me.TextBox1.InvokeThreadSafeMethod(New MethodInvoker(AddressOf TextBox1.Clear))
-
Apr 11th, 2011, 09:51 PM
#9
Re: [Extension] Set control properties across threads
The problem with that is that I still have to pass the arguments twice. For example, I have a UserControl with a TextBox with these two methods:
vb.net Code:
Public Class UserControl Public Sub TestMethod() TextBox1.Clear() End Sub Public Sub TestMethod(ByVal s As String) TextBox1.Text = s End Sub End Class
To call it, I need to do:
vb.net Code:
Private Sub RunInThread() Dim s = "Test" UserControl1.InvokeThreadSafeMethod(Sub() UserControl1.TestMethod()) UserControl1.InvokeThreadSafeMethod(Sub() UserControl1.TestMethod(s), s) End Sub
This works, the second UserControl sets the TextBox text correctly.
However, if I leave out the parameter, like so:
Code:
Private Sub RunInThread()
Dim s = "Test"
UserControl1.InvokeThreadSafeMethod(Sub() UserControl1.TestMethod(), s)
End Sub
then it calls the first overload without arguments and doesn't set any text, so the 's' argument is just ignored I guess.
I might also be able to pass an Action(Of String) to indicate I want the overload with a string argument, but then the users would still be creating the delegate themselves which is what I'm trying to avoid.
-
Apr 11th, 2011, 09:56 PM
#10
Re: [Extension] Set control properties across threads
Ok, I think I got it! I don't really understand how, but ok...
I got the extension method like this:
vb.net Code:
Private Delegate Sub InvokeThreadSafeMethodDelegate(ByVal control As Control, ByVal method As [Delegate]) <Extension()> _ Public Sub InvokeThreadSafeMethod(ByVal control As Control, ByVal method As [Delegate]) If (control.InvokeRequired) Then Dim del = New InvokeThreadSafeMethodDelegate(AddressOf InvokeThreadSafeMethod) control.Invoke(del, control, method) Else method.DynamicInvoke() End If End Sub
And I can call it like this:
vb.net Code:
Private Sub RunInThread() Dim s = "Test" UserControl1.InvokeThreadSafeMethod(Sub() UserControl1.TestMethod()) UserControl2.InvokeThreadSafeMethod(Sub() UserControl2.TestMethod(s)) End Sub
The first UserControl is clearing, the second one is setting the text.
-
Apr 11th, 2011, 11:35 PM
#11
Re: [Extension] Set control properties across threads
In the end I decided to pass the method as an Expression(Of Action), which I can then compile into a delegate and DynamicInvoke. For a function it's the same except it's passed as an Expression(Of Func(Of T)). The Compile step is probably quite slow (though I didn't do any speed tests) but I think it's neater than just accepting any delegate, especially since functions can now return their correct type instead of object.
-
Apr 21st, 2011, 10:36 PM
#12
Re: [Extension] Set control properties across threads
This looks great, Nick. I think it will help a lot of people out. I'm going to play around with it later, but I noticed that it's not very Option Strict On friendly.
Lines 27 and 47 is not allowed late binding with option strict on. GetMemberInfo doesn't have an 'As' clause, line 89 says, "Option Strict On disallows implicit conversions from 'System.Linq.Expressions.Expression' to 'System.Linq.Expressions.MemberExpression'.", GetPropertyInfo also doesn't have an 'As' clause, and line 98 is also disallowed because of late binding.
CodeBank contributions: Process Manager, Temp File Cleaner
Originally Posted by SJWhiteley
"game trainer" is the same as calling the act of robbing a bank "wealth redistribution"....
-
Apr 21st, 2011, 11:03 PM
#13
Re: [Extension] Set control properties across threads
Ugh, sorry about that. I am so used to working with option strict on that I don't even notice when it is actually off... And that's when the errors slip in
The only errors were actually the two As clauses in the functions (which in turn causes a ton of late binding errors), and one implicit conversion.
This was a straight translation from C#, that's how I always forget the As clause in functions (since C# has the type in front of the name). The implicit conversion of the Operand property to MemberExpression however seems to be valid in C# but not in VB, strange..? I added another cast there, but I still think it's weird that it's not required in C#.
Anyway, it should all work with option strict on now. I've also added the option infer statement at the top because I'm using it quite alot (another habit I picked up from C# and its var keyword), just in cause someone has it off in their settings, in which case my code would probably not compile.
Last edited by NickThissen; Apr 21st, 2011 at 11:07 PM.
-
Apr 22nd, 2011, 01:42 AM
#14
Re: [Extension] Set control properties across threads
Nick, you should set your VB defaults in the IDE Options, so that every new project has them set appropriately by default.
-
Apr 22nd, 2011, 08:27 AM
#15
Re: [Extension] Set control properties across threads
Originally Posted by jmcilhinney
Nick, you should set your VB defaults in the IDE Options, so that every new project has them set appropriately by default.
Yep, I know. The problem is when I install VS on a new computer (in this case a new laptop), I always forget because I simply don't make many errors like this anymore even when it is off. Then when I do make an error I don't notice it
-
Jun 11th, 2011, 10:39 PM
#16
Member
Re: [Extension] Set control properties across threads
Is there any way I can use this targeting the 2.0 framework?
-
Jun 12th, 2011, 12:50 AM
#17
Re: [Extension] Set control properties across threads
Originally Posted by JohnDorian
Is there any way I can use this targeting the 2.0 framework?
Extension methods don't exist prior to .NET 3.5. An extension method is just a regular method with an Extension attribute on it though. You can just declare a regular method and call it as a regular method.
-
Jun 12th, 2011, 03:51 AM
#18
Re: [Extension] Set control properties across threads
I think I'm using some more .NET 3.5 and higher stuff though. What about the System.Linq.Expressions namespace? I'm using Expression(Of Func(Of T)) for example, I don't think that's available in 2.0?
-
Jul 9th, 2011, 03:03 PM
#19
Re: [Extension] Set control properties across threads
Why limit it to the Control type? Why not let it use anything that implements the ISynchronizeInvoke interface? The methods and properties you are calling/testing, i.e. Invoke and InvokeRequired are just members of the ISynchronizeInvoke interface.
-
Jul 9th, 2011, 03:23 PM
#20
Re: [Extension] Set control properties across threads
Originally Posted by ForumAccount
Why limit it to the Control type? Why not let it use anything that implements the ISynchronizeInvoke interface? The methods and properties you are calling/testing, i.e. Invoke and InvokeRequired are just members of the ISynchronizeInvoke interface.
Good point! Don't know why I choose Control... Probably because I made this due to having need for it, and I needed it for controls only at that point. I should change it when I have some time, but I'll probably forget For now I'll add a link to this post so that people can see it can be used for any ISynchronizeInvoke object by just replacing every Control reference by ISynchronizeInvoke. It should be a quick fix (find/replace yay!) but I'd like to update the XML comments as well, I'll do it when I remember in the future
Last edited by NickThissen; Jul 9th, 2011 at 03:26 PM.
-
Jul 21st, 2011, 06:23 AM
#21
Fanatic Member
Re: [Extension] Set control properties across threads
Really nice method you got there, I thought the only way was using tons of overloaded delegates. Nice work.
Got a question somewhat related. Is it possible to make an "AsyncExecute" function, which runs the function/sub with the parameters specified? You may need to temporarily store the parameters and call the function from a subroutine that has no parameters. It is one of those things that would make certain operations a lot simpler, for example, you need to save data to a file asynchronously and the subroutine has a filepath argument.
At some point it may be useless (since you can't return data), but it could also allow for more flexible coding. And, you can add a callback property to the executor which will be called when the operation is completed!
Code:
AsyncExecute(Function() SaveFile("file.txt"), AddressOf SaveCompleted)
Last edited by bergerkiller; Jul 21st, 2011 at 06:29 AM.
-
Jul 22nd, 2011, 08:53 PM
#22
Re: [Extension] Set control properties across threads
Originally Posted by bergerkiller
Really nice method you got there, I thought the only way was using tons of overloaded delegates. Nice work.
Got a question somewhat related. Is it possible to make an "AsyncExecute" function, which runs the function/sub with the parameters specified? You may need to temporarily store the parameters and call the function from a subroutine that has no parameters. It is one of those things that would make certain operations a lot simpler, for example, you need to save data to a file asynchronously and the subroutine has a filepath argument.
At some point it may be useless (since you can't return data), but it could also allow for more flexible coding. And, you can add a callback property to the executor which will be called when the operation is completed!
Code:
AsyncExecute(Function() SaveFile("file.txt"), AddressOf SaveCompleted)
You should look at the new System.Threading.Tasks namespace in .NET 4.0 and the TaskFactory, Task and Task(Of TResult) classes in particular.
-
Jul 23rd, 2011, 11:28 AM
#23
Fanatic Member
Re: [Extension] Set control properties across threads
Trying to stay compatible with framework version 2.0, but since his example requires 3.5 it does not make sense, my bad.
I'll look into the newer Task namespaces then.
-
Oct 29th, 2011, 05:20 PM
#24
Member
Re: [Extension] Set control properties across threads
Nick,
Do you know why I can't use SetThreadSafeProperty with a ToolStripTextBox? It says that property isn't a member of 'System.Windows.Forms.ToolStripTextBox.'
I tried changing all of the control references to ISynchronizeInvoke, but a bunch of other errors occurred.
-
Oct 29th, 2011, 05:31 PM
#25
Re: [Extension] Set control properties across threads
I don't think ToolStripTextBox implements ISynchronizeInvoke and does not inherit Control either. I'm not too sure but I think you're out of luck here
-
Oct 29th, 2011, 11:40 PM
#26
Re: [Extension] Set control properties across threads
Originally Posted by JohnDorian
Nick,
Do you know why I can't use SetThreadSafeProperty with a ToolStripTextBox? It says that property isn't a member of 'System.Windows.Forms.ToolStripTextBox.'
I tried changing all of the control references to ISynchronizeInvoke, but a bunch of other errors occurred.
It is the Control class that implements ISynchronizeInvoke and a ToolStripTextBox is not a control. When you read the documentation for the ToolStripTextBox class you would have seen that and you also would have seen that it has a TextBox property, which refers to the TextBox control that it hosts. That TextBox is just a regular TextBox, so it is obviously a control.
-
Jun 27th, 2012, 04:45 PM
#27
Re: [Extension] Set control properties across threads
Really great stuff nick. Already has saved a lot of writing.
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|