Click here to Skip to main content
Licence CPOL
First Posted 27 Nov 2006
Views 59,579
Downloads 209
Bookmarked 29 times

Prevent 'cross-thread operation not allowed' exception through reflection

By | 18 Dec 2006 | Article
How to avoid 'cross-thread operation not allowed' errors using reflection.

Introduction

I was often faced with a problem on using System.Windows.Forms.Control objects: we can't perform operations on them in other threads than the one they were created. The only way I found to deal with this problem is to define a delegate and use it to invoke a method on the creation thread of the control (using the ISynchronizeInvoke interface of the Control itself).

The recurring solution

I wrote and wrote and wrote pieces of code like this:

Usual solution:

'The delegate method
Delegate Sub ChangeControlTextDelegate(ByVal ctrl As Control, _
                                       ByVal text As String)
'The method with the delegate signiture
Private Sub ChangeControlText(ByVal ctrl As Control, _
                              ByVal text As String) 
  If Me.InvokeRequired Then 
    Me.Invoke(New ChangeControlTextDelegate(AddressOf ChangeControlText), _
              New Object() {ctrl, text})
    Return
  End If
  ctrl.Text = text
End Sub

Writing a delegate for changing the Text property, one for changing the BackColor (great, I could reuse it for changing the ForeColor property too), and so on.

The solution using reflection

Naturally, the first step was to define my delegates once and forever in a class library so I could use them freely in forms, user defined controls, and so on, without having to redefine them. But, this approach still lacks in flexibility. So, I started to write a class which is able to take a control in its constructor and expose a method to set every non-parameterized property of the control by invoking a method with the appropriate signature, in the control's creation thread. Naturally, what such a class really needs in its Ctor isn't actually a Control, but just an object which implements the System.ComponentModel.ISynchronizeInvoke interface. I called it ThreadSafePropertySetter, and here is an example of how we can use it:

Sample code:

'in a Control class body we declare a ThreadSafePropertySetter object
private threadSafePropertySetter1 as threadSafePropertySetter
'...
'in the Control's Ctor we initialize it, passing the control (usually a windows form) itself
Public Sub New()
  ' Chiamata richiesta da Progettazione Windows Form.
  InitializeComponent()
  ' Aggiungere le eventuali istruzioni di inizializzazione
  ' dopo la chiamata a InitializeComponent().
  Me.threadSafePropertySetter1 = New ThreadSafePropertySetter(Me)
End Sub
'...
'In some method of the Form or control itself
Me.ThreadSafePropertySetter1.SetCtrlProperty(Of Color)(Me.button1, "BackColor", Color.Aqua)
Me.ThreadSafePropertySetter1.SetCtrlProperty(Of String)(Me.button1, "Text", "New text")

As you can see, I use Generics to get advantage from the type safety characteristics of the .NET platform. And now, let's look at how such a ThreadSafePropertySetter is implemented.

ThreadSafePropertySetterClass

'in a Control class body
Imports System.ComponentModel
Imports System.Reflection
Public Class ThreadSafePropertySetter

Delegate Sub SetCtrlPropertyDelegate(ByVal ctrl As Object, _
         ByVal propName As String, ByVal propvalue As Object)

Public Sub New(ByVal syncInvokeObject As ISynchronizeInvoke)
  Me._syncInvokeObject = syncInvokeObject
End Sub

Public Sub SetCtrlProperty(Of T)(ByVal ctrl As Object, _
       ByVal propName As String, ByVal propValue As T)
  SetObjectProperty(ctrl, propName, propValue)
End Sub

Protected Sub SetObjectProperty(ByVal obj As Object, _
          ByVal propertyName As String, ByVal propertyValue As Object)
  If _syncInvokeObject.InvokeRequired Then
    _syncInvokeObject.Invoke(New SetCtrlPropertyDelegate(AddressOf _
          SetCtrlProperty), New Object() {obj, propertyName, propertyValue})
    return
  End If
  Dim propInfo As PropertyInfo = obj.GetType.GetProperty(propertyName)
  If propInfo IsNot Nothing Then
    If propertyValue is Nothing Then
      propInfo.SetValue(obj, Nothing, Nothing)
    ElseIf propInfo.PropertyType.IsAssignableFrom(propertyValue.GetType) Then
      propInfo.SetValue(obj, propertyValue, Nothing)
    End If
  End If
End Sub

Private _syncInvokeObject As ISynchronizeInvoke

Naturally, you'll find a commented version of the class in the source code.

Further improvements

I will make the ThreasSafePropertySetter class implement the IExtenderProvider interface, and inherit the System.ComponentModel.Component class to give it Visual Studio Designer support. A disadvantage we have using the ThreasSafePropertySetter class is that we lose the IntelliSense support in setting properties: in effect, we have to pass to the SetCtrlProperty method the property name as a String, and there is a difference in setting a control's Text property this way:

button1.Text="cancel"

or this way:

Me.tsPropertySetter1.SetCtrlProperty(Of String)(button1, "Text", "cancel")

Any suggestions on how to retrieve the Intellisense support using the described approach?

I hope this small piece of code could be useful.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

siroman

Software Developer

Italy Italy

Member



Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
GeneralMy vote of 5 PinmemberJack_3217:49 9 Mar '12  
GeneralMy vote of 5 PinmemberStephan Fourie3:36 13 Dec '11  
GeneralNice PinmemberCIDev7:13 25 May '11  
General[Message Removed] PinmemberMojtaba Vali0:21 11 May '08  
GeneralRe: thanks Pinmembersiroman4:48 12 May '08  
GeneralPerformance [modified] Pinmembershaul_ahuva4:39 27 Dec '06  
GeneralRe: Performance PinmemberTrendyTim11:37 27 Dec '06  
GeneralRe: Performance Pinmembersiroman3:05 28 Dec '06  
QuestionInvoke Required use Pinmembertlongman11:17 26 Dec '06  
AnswerRe: Invoke Required use Pinmembersiroman2:24 27 Dec '06  
QuestionRe: Invoke Required use Pinmembertlongman12:10 28 Dec '06  
AnswerRe: Invoke Required use Pinmembersiroman21:11 1 Jan '07  
GeneralRe: Invoke Required use Pinmembertlongman17:09 2 Jan '07  
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
AnswerRe: Invoke Required use PinmemberFilip Duyck5:08 27 Dec '06  
GeneralRe: Invoke Required use Pinmembertlongman12:12 28 Dec '06  
GeneralISynchronizeInvoke PinmemberTrendyTim11:03 25 Dec '06  
GeneralRe: ISynchronizeInvoke PinmemberFilip Duyck5:09 27 Dec '06  
GeneralRe: ISynchronizeInvoke PinmemberTrendyTim11:17 27 Dec '06  
GeneralRe: ISynchronizeInvoke Pinmembersiroman2:33 28 Dec '06  
QuestionWhy ? PinmemberJuicy19:38 18 Dec '06  
AnswerRe: Why ? [modified] Pinmembersiroman2:07 19 Dec '06  
AnswerRe: Why ? PinmemberFilip Duyck4:24 19 Dec '06  
GeneralRe: Why ? Pinmembersiroman11:20 19 Dec '06  
GeneralRe: Why ? PinmemberFilip Duyck22:20 19 Dec '06  
GeneralRe: Why ? Pinmembersiroman2:49 20 Dec '06  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web01 | 2.5.120529.1 | Last Updated 18 Dec 2006
Article Copyright 2006 by siroman
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid