Results 1 to 5 of 5

Thread: [Extension] Set control properties across threads

  1. #1

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

    [Extension] Set control properties across threads

    [VB 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:
    csharp Code:
    1. using System;
    2. using System.Linq;
    3. using System.Linq.Expressions;
    4. using System.Reflection;
    5. using System.Windows.Forms;
    6.  
    7. namespace ThreadSafeExtensions
    8. {
    9.     public static class Extensions
    10.     {
    11.         private delegate void SetThreadSafePropertyDelegate<T>(
    12.             Control control, Expression<Func<T>> property, T value);
    13.         private delegate T GetThreadSafePropertyDelegate<T>(Control control, Expression<Func<T>> property);
    14.         private delegate void InvokeThreadSafeMethodDelegate(Control control, Expression<Action> method);
    15.         private delegate T InvokeThreadSafeFunctionDelegate<T>(Control control, Expression<Func<T>> function);
    16.  
    17.  
    18.         /// <summary>
    19.         /// Sets the specified property of this control to the specified value safely across threads by invoking a delegate if necessary.
    20.         /// </summary>
    21.         /// <typeparam name="T">The type of the property. Can usually be inferred from usage.</typeparam>
    22.         /// <param name="control">The control to set the property on.</param>
    23.         /// <param name="property">The property to set as a lambda expression.</param>
    24.         /// <param name="value">The new value of the property.</param>
    25.         public static void SetThreadSafeProperty<T>(this Control control, Expression<Func<T>> property, T value)
    26.         {
    27.             if (control.InvokeRequired)
    28.             {
    29.                 var del = new SetThreadSafePropertyDelegate<T>(SetThreadSafeProperty);
    30.                 control.Invoke(del, control, property, value);
    31.             }
    32.             else
    33.             {
    34.                 PropertyInfo propertyInfo = GetPropertyInfo(property);
    35.                 if (propertyInfo != null)
    36.                 {
    37.                     propertyInfo.SetValue(control, value, null);
    38.                 }
    39.             }
    40.         }
    41.  
    42.         /// <summary>
    43.         /// Gets the value of the specified property of this control safely across threads by invoking a delegate if necessary.
    44.         /// </summary>
    45.         /// <typeparam name="T">The type of the property. Can usually be inferred from usage.</typeparam>
    46.         /// <param name="control">The control to get the property from.</param>
    47.         /// <param name="property">The property to get the value from as a lambda expression.</param>
    48.         /// <returns>The value of the specified property.</returns>
    49.         public static T GetThreadSafeProperty<T>(this Control control, Expression<Func<T>> property)
    50.         {
    51.             if (control.InvokeRequired)
    52.             {
    53.                 var del = new GetThreadSafePropertyDelegate<T>(GetThreadSafeProperty);
    54.                 return (T) control.Invoke(del, control, property);
    55.             }
    56.             else
    57.             {
    58.                 PropertyInfo propertyInfo = GetPropertyInfo(property);
    59.                 if (propertyInfo != null)
    60.                 {
    61.                     return (T) propertyInfo.GetValue(control, null);
    62.                 }
    63.             }
    64.             return default(T);
    65.         }
    66.  
    67.         /// <summary>
    68.         /// Invokes a method of this control safely across threads by invoking a delegate if necessary.
    69.         /// </summary>
    70.         /// <param name="control">The control to invoke the method on.</param>
    71.         /// <param name="method">The method to invoke as an expression.</param>
    72.         public static void InvokeThreadSafeMethod(this Control control, Expression<Action> method)
    73.         {
    74.             if (control.InvokeRequired)
    75.             {
    76.                 var del = new InvokeThreadSafeMethodDelegate(InvokeThreadSafeMethod);
    77.                 control.Invoke(del, control, method);
    78.             }
    79.             else
    80.             {
    81.                 method.Compile().DynamicInvoke();
    82.             }
    83.         }
    84.  
    85.         /// <summary>
    86.         /// Invokes a function of this control safely across threads by invoking a delegate if necessary.
    87.         /// </summary>
    88.         /// <param name="control">The control to invoke the function on.</param>
    89.         /// <param name="function">The function to invoke as an expression</param>
    90.         public static T InvokeThreadSafeFunction<T>(this Control control, Expression<Func<T>> function)
    91.         {
    92.             if (control.InvokeRequired)
    93.             {
    94.                 var del = new InvokeThreadSafeFunctionDelegate<T>(InvokeThreadSafeFunction);
    95.                 return (T) control.Invoke(del, control, function);
    96.             }
    97.             else
    98.             {
    99.                 return (T) function.Compile().DynamicInvoke();
    100.             }
    101.         }
    102.        
    103.         private static MemberInfo GetMemberInfo(Expression expression)
    104.         {
    105.             MemberExpression memberExpression;
    106.             LambdaExpression lambda = (LambdaExpression)expression;
    107.             if (lambda.Body is UnaryExpression)
    108.             {
    109.                 UnaryExpression unaryExpression = (UnaryExpression)lambda.Body;
    110.                 memberExpression = (MemberExpression)unaryExpression.Operand;
    111.             }
    112.             else
    113.             {
    114.                 memberExpression = (MemberExpression)lambda.Body;
    115.             }
    116.  
    117.             return memberExpression.Member;
    118.         }
    119.  
    120.         private static PropertyInfo GetPropertyInfo(Expression expression)
    121.         {
    122.             MemberInfo memberInfo = GetMemberInfo(expression);
    123.             if (memberInfo.MemberType == MemberTypes.Property)
    124.             {
    125.                 return (PropertyInfo)memberInfo;
    126.             }
    127.             return null;
    128.         }
    129.     }
    130. }

    This looks complicated but usage is pretty easy. You'll need to pass the property as an lambda expression (which turns out to something like "() => <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:
    csharp Code:
    1. // Set label text
    2. label1.SetThreadSafeProperty(() => label1.Text, "test text");
    3.  
    4. // Disable button
    5. button1.SetThreadSafeProperty(() => button1.Enabled, false);
    6.  
    7. // Get TextBox text
    8. string txt = textBox1.GetThreadSafeProperty(() => textBox1.Text);
    9.  
    10. // Invoke some method
    11. customControl1.InvokeThreadSafeMethod(() => customControl1.SomeMethod("a", 3, 14.3));
    12.  
    13. // Invoke some function
    14. var x = customControl2.InvokeThreadSafeFunction(() => 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 get/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.

  2. #2

  3. #3

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

    Re: [Extension] Set control properties across threads

    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.

  4. #4
    Wait... what? weirddemon's Avatar
    Join Date
    Jan 2009
    Location
    USA
    Posts
    3,828

    Re: [Extension] Set control properties across threads

    Nick,

    I'm having trouble with .GetThreadSafeproperty. Here's the error:



    And this is how I'm using it:

    c# Code:
    1. foreach (ListViewItem lvi in this.lvwUserRightsMapping.GetThreadSafeProperty(() => this.lvwUserRightsMapping.Items))
    2.                 {
    3.                     this.lbLoadingSequence.InvokeThreadSafeMethod(() => this.lbLoadingSequence.Items.Add("Loading Sequence: " + lvi.Index.ToString()));
    4.                 }

    It's occuring on .Items.

    Any ideas?
    Last edited by weirddemon; Jan 16th, 2013 at 11:39 AM.
    CodeBank contributions: Process Manager, Temp File Cleaner

    Quote Originally Posted by SJWhiteley
    "game trainer" is the same as calling the act of robbing a bank "wealth redistribution"....

  5. #5

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

    Re: [Extension] Set control properties across threads

    I'm not sure so quickly what is happening but it seems like a rather complicated mess. My extensions were mainly used to access a simple property without having to worry about cross-thread issues, basically it saves you having to write all the "if invoke required then invoke" code. I can imagine it will go wrong quickly if you start using it for properties returning collections and then modifying collections in a loop. I'm sure in your case it would be easier to handle the cross-threading issues as you normally would (eg, don't use my extensions but write the appropriate code) instead of trying to adjust my code to make it work in such complicated circumstances. I may be wrong though

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