Results 1 to 27 of 27

Thread: [Extension] Set control properties across threads

Threaded View

  1. #1

    Thread Starter
    PowerPoster
    Join Date
    Apr 2007
    Location
    The Netherlands
    Posts
    5,070

    [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:
    1. Private Delegate Sub SetLabelTextDelegate(ByVal text As String)
    2.  
    3.     Private Sub SetLabelText(ByVal text As String)
    4.         If Label1.InvokeRequired Then
    5.             Label1.Invoke(New SetLabelTextDelegate(AddressOf SetLabelText), text)
    6.         Else
    7.             Label1.Text = text
    8.         End If
    9.     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:
    1. Option Strict On
    2. Option Infer On
    3.  
    4. Imports System.Linq.Expressions
    5. Imports System.Reflection
    6. Imports System.Runtime.CompilerServices
    7.  
    8. Module Extensions
    9.  
    10.     Private Delegate Sub SetThreadSafePropertyDelegate(Of T)(ByVal control As Control, ByVal [property] As Expression(Of Func(Of T)), ByVal value As T)
    11.     Private Delegate Function GetThreadSaferopertyDelegate(Of T)(ByVal control As Control, ByVal [property] As Expression(Of Func(Of T))) As T
    12.     Private Delegate Sub InvokeThreadSafeMethodDelegate(ByVal control As Control, ByVal method As Expression(Of Action))
    13.     Private Delegate Function InvokeThreadSafeFunctionDelegate(Of T)(ByVal control As Control, ByVal [function] As Expression(Of Func(Of T))) As T
    14.  
    15.     ''' <summary>
    16.     ''' Sets the specified property of this control to the specified value safely across threads by invoking a delegate if necessary.
    17.     ''' </summary>
    18.     ''' <typeparam name="T">The type of the property. Can usually be inferred from usage.</typeparam>
    19.     ''' <param name="control">The control to set the property on.</param>
    20.     ''' <param name="property">The property to set as a lambda expression.</param>
    21.     ''' <param name="value">The new value of the property.</param>
    22.     <Extension()> _
    23.     Public Sub SetThreadSafeProperty(Of T)(ByVal control As Control, ByVal [property] As Expression(Of Func(Of T)), ByVal value As T)
    24.         If (control.InvokeRequired) Then
    25.             Dim del = New SetThreadSafePropertyDelegate(Of T)(AddressOf SetThreadSafeProperty)
    26.             control.Invoke(del, control, [property], value)
    27.         Else
    28.             Dim propertyInfo = GetPropertyInfo([property])
    29.             If (propertyInfo IsNot Nothing) Then
    30.                 propertyInfo.SetValue(control, value, Nothing)
    31.             End If
    32.         End If
    33.     End Sub
    34.  
    35.     ''' <summary>
    36.     ''' Gets the value of the specified property of this control safely across threads by invoking a delegate if necessary.
    37.     ''' </summary>
    38.     ''' <typeparam name="T">The type of the property. Can usually be inferred from usage.</typeparam>
    39.     ''' <param name="control">The control to get the property from.</param>
    40.     ''' <param name="property">The property to get the value from as a lambda expression.</param>
    41.     ''' <returns>The value of the specified property.</returns>
    42.     <Extension()> _
    43.     Public Function GetThreadSafeProperty(Of T)(ByVal control As Control, ByVal [property] As Expression(Of Func(Of T))) As T
    44.         If (control.InvokeRequired) Then
    45.             Dim del = New GetThreadSaferopertyDelegate(Of T)(AddressOf GetThreadSafeProperty)
    46.             Return DirectCast(control.Invoke(del, control, [property]), T)
    47.         Else
    48.             Dim propertyInfo = GetPropertyInfo([property])
    49.             If (propertyInfo IsNot Nothing) Then
    50.                 Return DirectCast(propertyInfo.GetValue(control, Nothing), T)
    51.             End If
    52.         End If
    53.         Return Nothing
    54.     End Function
    55.  
    56.     ''' <summary>
    57.     ''' Invokes a method of this control safely across threads by invoking a delegate if necessary.
    58.     ''' </summary>
    59.     ''' <param name="control">The control to invoke the method on.</param>
    60.     ''' <param name="method">The method to invoke as an expression.</param>
    61.     <Extension()> _
    62.     Public Sub InvokeThreadSafeMethod(ByVal control As Control, ByVal method As Expression(Of Action))
    63.         If (control.InvokeRequired) Then
    64.             Dim del = New InvokeThreadSafeMethodDelegate(AddressOf InvokeThreadSafeMethod)
    65.             control.Invoke(del, control, method)
    66.         Else
    67.             method.Compile().DynamicInvoke()
    68.         End If
    69.     End Sub
    70.  
    71.     ''' <summary>
    72.     ''' Invokes a function of this control safely across threads by invoking a delegate if necessary.
    73.     ''' </summary>
    74.     ''' <typeparam name="T">The return type of the function to invoke. Can usually be inferred from usage.</typeparam>
    75.     ''' <param name="control">The control to invoke the function on.</param>
    76.     ''' <param name="function">The function to invoke as an expression.</param>
    77.     ''' <returns>The result of the function to invoke.</returns>
    78.     <Extension()> _
    79.     Public Function InvokeThreadSafeFunction(Of T)(ByVal control As Control, ByVal [function] As Expression(Of Func(Of T))) As T
    80.         If (control.InvokeRequired) Then
    81.             Dim del = New InvokeThreadSafeFunctionDelegate(Of T)(AddressOf InvokeThreadSafeFunction)
    82.             Return DirectCast(control.Invoke(del, control, [function]), T)
    83.         Else
    84.             Return DirectCast([function].Compile().DynamicInvoke(), T)
    85.         End If
    86.     End Function
    87.  
    88.     Private Function GetMemberInfo(ByVal expression As Expression) As MemberInfo
    89.         Dim memberExpression As MemberExpression
    90.         Dim lambda = DirectCast(expression, LambdaExpression)
    91.         If (TypeOf lambda.Body Is UnaryExpression) Then
    92.             memberExpression = DirectCast(DirectCast(lambda.Body, UnaryExpression).Operand, MemberExpression)
    93.         Else
    94.             memberExpression = DirectCast(lambda.Body, MemberExpression)
    95.         End If
    96.         Return memberExpression.Member
    97.     End Function
    98.  
    99.     Private Function GetPropertyInfo(ByVal expression As Expression) As PropertyInfo
    100.         Dim memberInfo = GetMemberInfo(expression)
    101.         If (memberInfo.MemberType = Reflection.MemberTypes.Property) Then
    102.             Return DirectCast(memberInfo, PropertyInfo)
    103.         End If
    104.         Return Nothing
    105.     End Function
    106.  
    107. 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:
    1. ' Set label text
    2. Label1.SetThreadSafeProperty(Function() Label1.Text, "test text")
    3.  
    4. ' Disable button
    5. Button1.SetThreadSafeProperty(Function() Button1.Enabled, false)
    6.  
    7. ' Get textbox text
    8. Dim text As String = TextBox1.GetThreadSafeProperty(Function() TextBox1.Text)
    9.  
    10. ' Invoke some method (note: Sub() instead of Function() !)
    11. CustomControl1.InvokeThreadSafeMethod(Sub() CustomControl1.SomeMethod("a", 3))
    12.  
    13. ' Invoke some function
    14. 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.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width