Click here to Skip to main content
Click here to Skip to main content

A Self Contained Thread Safe TextBox Control

By , 16 Nov 2007
Rate this:
Please Sign up or sign in to vote.
Screenshot - ThreadSafeTextBoxDemoScreenShot.jpg

Introduction

In many projects, I find that I am quickly growing tired of writing delegates to keep various controls thread safe in multi-threaded environments. I have not yet determined whether it is best to create separate utility classes to handle all of the non-thread safe functionality, or to extend the control itself to be thread safe by default. While writing this sample, and seeing how incredibly simple it is to make a control thread safe, I am baffled as to why Microsoft did not build this in from the start.

Extending the TextBox Control

While planning this project I thought, I can simply derive from a TextBox, override the necessary properties and methods with thread safe versions, and be done with it. First, let's take a look at the Text property, which I was able to use this method for. In the derived TextBox, we override Text as such:

public override string Text
{
  get
  {
    return base.Text;
  }
  set
  {
    if (InvokeRequired)
    {
      TextDelegate callback = SafeSetText;
      BeginInvoke(callback, new object[] { value });
    }
    else
      base.Text = value;
  }
}

And our helper method with delegate:

private delegate void TextDelegate(string text);
private void SafeSetText(string text)
{
  base.Text = text;
}

The above code is really all that is required if you only need it so set the Text property of the control. The method I am using here is *almost* the exact same way that it would be done in the main application code, each and every time this ability was needed. When the Text property is assigned to, we check InvokeRequired to see if we are on the UI thread or not. If we are, just set the text and be done with it. If we are not, we then call BeginInvoke and pass the value on to our helper method SafeSetText which then assigns the property on the UI thread.

A Step Further

Being cocky, here I thought my work was done. WRONG!
Next up, AppendText. Already I ran into a problem with AppendText. If you don't already know, AppendText is implemented in TextBoxBase. On instinct, my first attempt was to subclass TextBoxBase as well, but as it turns out, this class cannot be derived from. However, all hope is not lost. After poking around a little bit, this was quite easily worked around, as such:

public new void AppendText(string AppendingText)
{
  Text += AppendingText;
}

This method effectively hides the internal AppendText method. Being that we already handle the Text property, as you see above, this method is implemented with one line of code.

As sdahlbac pointed out, while it does work, using .Text += brings up some performance issues, especially if the text becomes rather long. After poking around the Framework source and trying a few things, I managed to crack this one using an overly complicated method, as another reader pointed out. Let's have a look at the correct way to do it.

public new void AppendText(string text)
{
    if (!string.IsNullOrEmpty(text))
    {
        if (this.InvokeRequired)
        {
            TextDelegate callback = base.AppendText;
            this.BeginInvoke(callback, new object[] { text });
        }
        else
        {
            base.AppendText(text);
        }
    }
}

And there you have it.

The other members implemented are ForeColor, BackColor, and Clear. The implementation is very similar to that above so I am not including it in the article.

Further Extending

The method used here can be applied to virtually any control. The only thing to remember is to override members that directly belong to the control you are extending, and to replace those that you cannot override. I am certain I have not covered all members that may need to be thread safe, but these are the most used.

In Conclusion

I have not yet had the chance to test how this control performs in the event that it is instantiated from a non UI thread. My assumption at this point is that my efforts here will be moot in that case. Once I am able to test that, I will update with my findings. However, for any cases where the control is placed on the form at design time, or at least created on the UI thread, there should be no issues.

An idea I started toying with was to programmatically determine the form that the control is a child of and invoke on that control rather than the TextBox control. Doing so would eliminate the possibility of the TextBox calling BeginInvoke from outside the UI thread if it was created at run time. So far I have not been able to implement this successfully. I will likely add it as soon as I figure it out or someone posts the solution in the comments.

History

  • 11-08-2007 - Initial submission
  • 11-09-2007 - Eliminated potential performance issues with AppendText
  • 11-16-2007 - Replaced overly complex AppendText method

License

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

About the Author

Jim Weiler
Software Developer
United States United States
No Biography provided

Comments and Discussions

 
QuestionThread Safe Application Pinmemberramarch26-Nov-07 18:27 
GeneralNormalisation of behaviour [modified] PinmemberAlex Cater25-Nov-07 8:12 
Firstly, congratulations on an article that is well written and to the point.
 
I wont try to prescribe one technique over another for handling this issue but I will say that ending up with similar synchronisation code within other members of the control should start alarm bells ringing. Considering that the goal of object oriented design is to avoid replication of behaviour, it would certainly be better to refactor the code to minimise this as much as possible.
 
The following class is an example of how we might attempt to reduce the replication:
 
public class Invoker<T> 
    where T : Control
{
    private T owner;
 
    public Invoker(T control)
    {
        if (control == null) { throw new ArgumentNullException("control"); }
 
        this.owner = control;
    }
 
    // Support our StackTrace usage by ensuring we aren't inlined by the JIT. 
    [MethodImpl(MethodImplOptions.NoInlining)]
    public bool SafeInvoke(params object[] parameters)
    {
        bool safe = true;
        if (owner.InvokeRequired)
        {
            // Fetch the types of each parameter to help identify the caller.
            Type[] types = Array.ConvertAll<object, Type>(parameters, 
                delegate(object obj) { return obj.GetType(); });
 
            // Identify, obtain and invoke the calling method.
            string name = new StackTrace().GetFrame(1).GetMethod().Name;
            BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
            MethodInfo method = typeof(T).GetMethod(name, bindingAttr, null, types, null);
            owner.BeginInvoke(new MethodInvoker(delegate
            {
                method.Invoke(owner, parameters);
            }));
            safe = false;
        }
        return safe;
    }
}
 
The code makes use of reflection, which is much maligned as being slow. Reflection is, of course, slower than making a native method call but it offers other significant benefits. This example is meant to address the duplication of behaviour rather than provide any sort of optimisation.
 
The class is intended to be used as a contained helper object, for example:
 
public class TextBoxEx : TextBox
{
    private Invoker<TextBoxEx> invoker;
 
    public TextBoxEx()
    {
        // Create the synchronisation helper.
        invoker = new Invoker<TextBoxEx>(this);
    }
 
    public override string Text
    {
        get
        {
            return base.Text;
        }
        set
        {
            // SafeInvoke returns true when it's running on 
            // the thread that owns the control.
            // Note: We must call SafeInvoke with the parameters that 
            // were passed to this method.
            if (invoker.SafeInvoke(value))
            {
                base.Text = value;
            }
        }
    }
}
 
The class supports NET 2.0 onwards due to the fact that it makes use of generics and anonymous methods.
 
The previous AppendText example...
 
public new void AppendText(string text)
{
    if (!string.IsNullOrEmpty(text))
    {
        if (this.InvokeRequired)
        {
            TextDelegate callback = base.AppendText;
            this.BeginInvoke(callback, new object[] { text });
        }
        else
        {
            base.AppendText(text);
        }
    }
}
 
would become:
 
public new void AppendText(string text)
{
    if (invoker.SafeInvoke(text))
    {
        base.AppendText(text);
    }
}
 
Note: I have also excluded the IsNullOrEmpty check as I believe it could mask bugs. For example, if null was passed would you expect an exception to be thrown or nothing happen?? I personally believe that an exception should be allowed to occur unless there is a very good reason why it should be 'swallowed'. In addition, I'm certain the base class TextBoxBase does not check for null.
 
Alex
 
-- modified at 4:53 Friday 30th November, 2007
 
----------------------------------
ASCII silly question, get a silly ANSI.

GeneralRe: usage scenarios ? Pinmemberjmacdona13-Nov-07 3:23 
Questionusage scenarios ? PinmemberBillWoodruff12-Nov-07 20:30 
AnswerRe: usage scenarios ? PinmemberJim Weiler12-Nov-07 21:14 
GeneralAppendText performance implications Pinmembersdahlbac8-Nov-07 22:13 
GeneralRe: AppendText performance implications PinmemberJim Weiler9-Nov-07 3:25 
AnswerRe: AppendText performance implications [modified] PinmemberJim Weiler9-Nov-07 6:59 
GeneralRe: AppendText performance implications PinmemberRichard Deeming13-Nov-07 8:21 
GeneralRe: AppendText performance implications PinmemberJim Weiler13-Nov-07 10:26 
GeneralIf you found this helpful please vote! PinmemberJim Weiler8-Nov-07 11:47 

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.

| Advertise | Privacy | Mobile
Web03 | 2.8.140415.2 | Last Updated 16 Nov 2007
Article Copyright 2007 by Jim Weiler
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid