Click here to Skip to main content
15,917,062 members
Articles / Desktop Programming / Windows Forms
Article

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

Rate me:
Please Sign up or sign in to vote.
3.46/5 (13 votes)
18 Dec 2006CPOL2 min read 114.7K   384   30   29
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:

VB
'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:

VB
'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

VB
'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:

VB
button1.Text="cancel"

or this way:

VB
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)


Written By
Software Developer
Italy Italy
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
AnswerRe: Why ? Pin
Filip Duyck19-Dec-06 4:24
Filip Duyck19-Dec-06 4:24 
GeneralRe: Why ? Pin
siroman19-Dec-06 11:20
siroman19-Dec-06 11:20 
GeneralRe: Why ? Pin
Filip Duyck19-Dec-06 22:20
Filip Duyck19-Dec-06 22:20 
GeneralRe: Why ? Pin
siroman20-Dec-06 2:49
siroman20-Dec-06 2:49 
GeneralRe: Why ? Pin
Filip Duyck27-Dec-06 5:03
Filip Duyck27-Dec-06 5:03 
GeneralRe: Why ? Pin
siroman27-Dec-06 5:33
siroman27-Dec-06 5:33 
GeneralRe: Why ? Pin
Filip Duyck27-Dec-06 5:40
Filip Duyck27-Dec-06 5:40 
GeneralRe: Why ? Pin
siroman27-Dec-06 22:48
siroman27-Dec-06 22:48 
OK, OK. i don't want to pick up a discussion with you, and I'm sure there's nothing personal, there's no reason. But I want to point out some arguments. First of all I know Vb.net. Just because English is not my spoken language and I use sentences like "at least so I know", "I think" or "in my opinion" to introduce some irony or sarcasm, or simply to limit my great ego, it doesn't mean that I don't know the language. Believe me, there's no anonymous methods in Vb.net, it's a wellknown argument, and there's no way to pass some parameters to a parameterless function, whatever language you use. I know the performance problems of the approach we are discussing here, but what I want to achieve is not simply performance but a more elegant, with less duplicated code, solution: such a solution could be used when some performace may be sacrificed (as some reader is pointing out in their comments). Sorry, but I'm aware that this is not a forum and although this I will post articles again in the future. I red many articles on TCP with no sense at all, or copied from other websites, or with no source code, or, worst, with source code that doesn't work: it is not the case with this article. If you think I wrote something wrong, tell me where and, if possible, I'll correct it and update the article. If you know better ways to achieve the goal of the article (in vb.net, not C#) let me know and I'll update the article. I'm not the newbie you think, but I honestly admit (perhaps this is my error?) that my knowledge is not the same level of that of, e.g., Francesco Balena (author of many books on Vb.Net, C# and the .Net Framework). Such a knowledge is one of my goals, but I won't await to achieve it to post articles and, once reached, I won't feel like I've nothing to learn again. What I meant is that I don't want to place myself on the top of a podium to tell you what to learn and how to behave; please do the same. Let's speak about code, not about pats on the back. Curiously I feel you commented the article this way most because of my way to write replies or my naif signiture than because of the contents itself, and, curiously, i feel you yourself don't know Vb.Net very much, posting code in C# on an article based on VB.net, but this is only my opinion.

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

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