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

Tagged as

Go to top

Windows Forms Thread Safety: InvalidOperationException and Invoking

, 6 Mar 2012
Rate this:
Please Sign up or sign in to vote.
If you are developing with Windows Forms, you know that due to thread safety reasons you can not change any UI control’s settings from threads different from UI thread itself. Assuming you have a multithreaded application where you want to acquire some values in parallel and then display them

If you are developing with Windows Forms, you know that due to thread safety reasons you can not change any UI control’s settings from threads different from UI thread itself. imageAssuming you have a multithreaded application where you want to acquire some values in parallel and then display them on the UI elements. It could be some server requests (even simultaneous requests to different servers) or just some calculations – it doesn’t really matter now. What matters is that you need to apply some values to the same controls several times (applying text to several "labels" or "text boxes").

For our simple case let’s assume the situation when you have several "labels" and "combo boxes". During application startup some calculations will be done and results will be put inside combo boxes. Each label will be marked in according to the it's calculation. As a calculation example let’s take the simple progression of "x" order. (order = 1: 1, 1, 1, 1, ...; order = 2: 2, 4, 8, 16, ...'; order = 3: 3, 9, 27, 81; etc.) The task is to calculate each sequence in a separate thread.

For somebody who doesn’t familiar with invoking, the first and naïve solution could be something like this:

using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows.Forms;
 
namespace WindowsFormsInvoking
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
 
        private void Form1_Load(object sender, EventArgs e)
        {
            Thread t1 = new Thread(ApplyUiValues);
            Thread t2 = new Thread(ApplyUiValues);
            Thread t3 = new Thread(ApplyUiValues);
            Thread t4 = new Thread(ApplyUiValues);
            Thread t5 = new Thread(ApplyUiValues);
 
            t1.Start(1.0);
            t2.Start(2.0);
            t3.Start(3.0);
            t4.Start(4.0);
            t5.Start(5.0);
        }
 
        private void ApplyUiValues(object order)
        {
            if (order is Double)
            {
                Double o = Convert.ToInt32(order);
                string labelStr = string.Format("Order {0}", o);
                List<Double> values = new List<Double>();
                List<string> strings = new List<string>();
 
                values = GenerateOneToTenSequense(order);
                foreach (Double i in values)
                {
                    strings.Add(i.ToString());
                }
 
                switch ((int)o)
                {
                    case 1:
                        label1.Text = labelStr;
                        comboBox1.Items.AddRange(strings.ToArray());
                        break;
                    case 2:
                        label2.Text = labelStr;
                        comboBox2.Items.AddRange(strings.ToArray());
                        break;
                    case 3:
                        label3.Text = labelStr;
                        comboBox3.Items.AddRange(strings.ToArray());
                        break;
                    case 4:
                        label4.Text = labelStr;
                        comboBox4.Items.AddRange(strings.ToArray());
                        break;
                    case 5:
                        label5.Text = labelStr;
                        comboBox5.Items.AddRange(strings.ToArray());
                        break;
                    default:
                        break;
                }
            }
        }
 
        private List<Double> GenerateOneToTenSequense(object order)
        {
            List<Double> sequence = new List<Double>();
            if (order is Double)
            {
                Double o = Convert.ToInt32(order);
                for (Double i = 1; i <= 10; ++i)
                {
                    sequence.Add(Math.Pow(o, i));
                }
            }
            return sequence;
        }
    }
}

But once you’ll try to run this, you’ll immediately get the following exception:

image

InvalidOperationException – you can not change UI properties from other threads. MSDN provides a very well explanation on How to: Make Thread-Safe Calls to Windows Forms Controls

But there is something missing. For example – do I really need to call "Background Worker" for this simple things? What if I have many similar controls (like in my example) – should I repeatedly call InvokeRequired property? I just want to use the simple one line of code similar to this one:

label1.Text = labelStr;

So my solution was to create a helper class with static methods to call most often used controls:

using System;
using System.Collections.Generic;
using System.Windows.Forms;
 
namespace WindowsFormsInvoking
{
    public class Invoker
    {
        #region delegates
 
        delegate void SetComboBoxItemsCallback(
            out bool isSet, 
            ComboBox invokingCtrl, 
            List<string> items);
        
        delegate void SetComboBoxSelectedItemCallback(
            out bool isSet, 
            ComboBox invokingCtrl, 
            int itmToSet);
        
        delegate void TrySetLabelTextCallback(
            out bool isSet, 
            Label invokingCtrl, 
            string valToSet);
 
        #endregion
 
        /// <summary>
        /// Loads Items for a ComboBox from List of strings
        /// </summary>
        /// <param name="caller">Parent control (usually it's a Form)</param>
        /// <param name="invokingCtrl">ComboBox to load items to</param>
        /// <param name="items">Strings to be loaded into the ComboBox</param>
        public static void LoadComboBoxItems(
            Control caller,
            ComboBox invokingCtrl,
            List<string> items)
        {
            try
            {
                bool wasSet = false;
                SetComboBoxItems(out wasSet, invokingCtrl, items);
                if (!wasSet)
                {
                    SetComboBoxItemsCallback cb = 
                        new SetComboBoxItemsCallback(SetComboBoxItems);
                    caller.Invoke(cb, 
                        new object[] { wasSet, invokingCtrl, items });
                }
 
                if (invokingCtrl.Items.Count > 0)
                {
                    Invoker.SetComboBoxSelectedItem(
                        out wasSet, 
                        invokingCtrl, 0);
 
                    if (!wasSet)
                    {
                        SetComboBoxSelectedItemCallback cb =
                            new SetComboBoxSelectedItemCallback(
                                Invoker.SetComboBoxSelectedItem);
 
                        caller.Invoke(cb, 
                            new object[] { wasSet, invokingCtrl, 0 });
                    }
                }
            }
            catch (Exception ex)
            {
                
            }
        }
 
        /// <summary>
        /// Sets a selected item for a ComboBox
        /// </summary>
        /// <param name="caller">Parent control (usually it's a Form)</param>
        /// <param name="invokingCtrl">Label to set text to</param>
        /// <param name="valToSet">String to set</param>
        public static void SetLabelText(
            Control caller,
            Label invokingCtrl,
            string valToSet)
        {
            try
            {
                bool wasSet = false;
                Invoker.TrySetLabelText(out wasSet, 
                    invokingCtrl, valToSet);
                if (!wasSet)
                {
                    TrySetLabelTextCallback cb =
                        new TrySetLabelTextCallback(Invoker.TrySetLabelText);
                    caller.Invoke(cb, new object[] { wasSet, invokingCtrl, valToSet });
                }
            }
            catch (Exception ex)
            {
                
            }
        }
 
        private static void SetComboBoxItems(
            out bool isSet, 
            ComboBox invokingCtrl, 
            List<string> items)
        {
            if (!invokingCtrl.InvokeRequired && 
                invokingCtrl.Items.Count == 0)
            {
                invokingCtrl.Items.AddRange(items.ToArray());
                isSet = true;
            }
 
            isSet = false;
        }
 
        private static void SetComboBoxSelectedItem(
            out bool isSet, 
            ComboBox invokingCtrl, 
            int itmToSet)
        {
            if (!invokingCtrl.InvokeRequired)
            {
                invokingCtrl.SelectedItem = invokingCtrl.Items[itmToSet];
                isSet = true;
            }
 
            isSet = false;
        }
 
        private static void TrySetLabelText(
            out bool isSet, 
            Label invokingCtrl, 
            string valToSet)
        {
            if (!invokingCtrl.InvokeRequired)
            {
                invokingCtrl.Text = valToSet;
                isSet = true;
            }
 
            isSet = false;
        }
    }
}

After creating the helper Invoker class I’m able to use the property assignment in one line like I wanted:

Invoker.SetLabelText(this, label1, labelStr);

And the result is:

private void ApplyUiValues(object order)
{
    if (order is Double)
    {
        Double o = Convert.ToInt32(order);
        string labelStr = string.Format("Order {0}", o);
        List<Double> values = new List<Double>();
        List<string> strings = new List<string>();
 
        values = GenerateOneToTenSequense(order);
        foreach (Double i in values)
        {
            strings.Add(i.ToString());
        }
 
        switch ((int)o)
        {
            case 1:
                Invoker.SetLabelText(this, label1, labelStr);
                Invoker.LoadComboBoxItems(this, comboBox1, strings);
                break;
            case 2:
                Invoker.SetLabelText(this, label2, labelStr);
                Invoker.LoadComboBoxItems(this, comboBox2, strings);
                break;
            case 3:
                Invoker.SetLabelText(this, label3, labelStr);
                Invoker.LoadComboBoxItems(this, comboBox3, strings);
                break;
            case 4:
                Invoker.SetLabelText(this, label4, labelStr);
                Invoker.LoadComboBoxItems(this, comboBox4, strings);
                break;
            case 5:
                Invoker.SetLabelText(this, label5, labelStr);
                Invoker.LoadComboBoxItems(this, comboBox5, strings);
                break;
            default:
                break;
        }
    }
}

image

You can download this example code from Max's SVN :http://subversion.assembla.com/svn/max-s-blog-posts/

Please leave your comment if you found this post helpful.

Thanks and enjoy your coding! Max B.

License

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

Share

About the Author

bovykinmaxim

United States United States
No Biography provided

Comments and Discussions

 
QuestionPlease provide your download as zip archiv PinmemberRenate Neumann29-Feb-12 9:10 
AnswerRe: Please provide your download as zip archiv Pinmemberbovykinmaxim6-Mar-12 3:05 

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
Web02 | 2.8.140916.1 | Last Updated 6 Mar 2012
Article Copyright 2012 by bovykinmaxim
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid