SoFunction
Updated on 2025-03-08

Implementation method of calling winform form control in multithread

This article describes the method of calling winform form controls in multithreads in C#, which has good reference value for learning C# programming. The specific methods are as follows:

First of all, because Windows Forms controls are not thread-safe in nature. Therefore, if two or more threads operate the set value of a control moderately, the control may be forced into an inconsistent state. Other thread-related bugs may also occur, including contention and deadlock. So when running the application in the debugger, if another thread other than the thread that created a control attempts to call the control, the debugger will raise an InvalidOperationException

This article uses a very simple example to explain this problem (put a TextBox and a Button on the form, click Button, set the TextBox value in the newly created thread)

Solution 1: Turn off the exception detection method to avoid the occurrence of exceptions

After testing, it was found that although this method avoids exception throwing, it cannot guarantee the correctness of the program's running results (for example, when multiple threads set TextBox1's Text at the same time, it is difficult to predict what TextBox1's Text will be in the end)

using System;
using ;
using ;
using ;
using ;
using ;
using ;
using ;
namespace winformTest
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();
       = false;//This line is the key    }
    

    private void button1_Click(object sender, EventArgs e)
    {
      SetTextBoxValue();
    }

    void SetTextBoxValue()
    {
      TextBoxSetValue tbsv = new TextBoxSetValue(this.textBox1, "Method1");
      ThreadStart TS = new ThreadStart();
      Thread T = new Thread(TS);
      ();
    }


    class TextBoxSetValue
    {
      private TextBox _TextBox ;
      private string _Value;

      public TextBoxSetValue(TextBox TxtBox, String Value) 
      {
        _TextBox = TxtBox;
        _Value = Value;
      }

      public void SetText() 
      {
        _TextBox.Text = _Value;
      }
    }
  }
}

Solution 2: Secure call through delegation

using System;
using ;
using ;
using ;
using ;
using ;
using ;
namespace winformTest
{
  public partial class Form2 : Form
  {
    public Form2()
    {
      InitializeComponent();
    }
    private void button1_Click(object sender, EventArgs e)
    {
      SetTextBoxValue();
    }    
    private delegate void CallSetTextValue();
    //Call through delegate    void SetTextBoxValue() 
    {
      TextBoxSetValue tbsv = new TextBoxSetValue(this.textBox1, "Method2");
      if ()
      {
        CallSetTextValue call = new CallSetTextValue();
        (call);        
      }
      else
      {
        ();
      }
    }
    class TextBoxSetValue
    {
      private TextBox _TextBox;
      private string _Value;
      public TextBoxSetValue(TextBox TxtBox, String Value)
      {
        _TextBox = TxtBox;
        _Value = Value;
      }
      public void SetText()
      {
        _TextBox.Text = _Value;
      }
      public TextBox TextBox {
        set { _TextBox = value; }
        get { return _TextBox; }
      }      
    }
  }
}

The third solution: use the BackgroundWorker control

using System;
using ;
using ;
using ;
using ;
using ;
using ;
using ;
namespace winformTest
{
  public partial class Form3 : Form
  {
    public Form3()
    {
      InitializeComponent();
    }
    private void button1_Click(object sender, EventArgs e)
    { 
      using (BackgroundWorker bw = new BackgroundWorker())
      {
         += SetTextBoxValue;
        ();
      }
    } 
    void SetTextBoxValue(object sender, RunWorkerCompletedEventArgs e) 
    {
      TextBoxSetValue tbsv = new TextBoxSetValue(this.textBox1, "Method3");
      ();
    }
    class TextBoxSetValue
    {
      private TextBox _TextBox;
      private string _Value;
      public TextBoxSetValue(TextBox TxtBox, String Value)
      {
        _TextBox = TxtBox;
        _Value = Value;
      }
      public void SetText()
      {
        _TextBox.Text = _Value;
      }
    }
  }
}

Users do not like slow-responsive programs. When performing long-term operations, using multithreading is wise, it can improve the response speed of the program UI and make everything run faster. Multithreaded programming in Windows was once an exclusive privilege for C++ developers, but now it can be written in all Microsoft .NET-compatible languages.

However, Windows Forms architecture sets strict rules for thread usage. If you are just writing a single threaded application, you don't need to know these rules, because single threaded code cannot violate these rules. However, once multithreading is adopted, one of the most important threading rules in Windows forms needs to be understood: do not use any member of the control in a thread other than its creation thread, with very few exceptions. Exceptions to this rule are documented, but this is very rare. This applies to any object whose class derives from, which includes almost all elements in the UI. All UI elements (including the form itself) are objects derived from the Control class. Furthermore, the result of this rule is that a contained control (such as a button contained in a form) must be in the same thread as the containing control bit. That is, all controls in a window belong to the same UI thread. In fact, most Windows Forms applications end up with only one thread, and all UI activities occur on this thread. This thread is usually called a UI thread. This means that you cannot call any method on any control in the user interface unless it is stated in the documentation for that method. Exceptions to this rule (always documented) are very few and there is little relationship between them. Please note that the following code is illegal:

private Thread myThread;
private void Form1_Load(object sender, EventArgs e)
{
  myThread = new Thread(new ThreadStart(RunsOnWorkerThread));
  ();
}
private void RunsOnWorkerThread()
{
   = "myThread thread calls UI control";
}

If you try to run this code in .NET Framework version 1.0, you may run it successfully or it may appear to be the case at first. This is the main problem in multithreaded errors, i.e. they don't appear immediately. Even when some errors come up, everything looks normal before the first demo. But don't make a mistake — the code I just showed clearly violates the rules, and it is foreseeable that anyone who hopes that "the trial run is good, there should be no problem" will pay a heavy price in the upcoming debugging period.

Let’s take a look at what methods can solve this problem.

1. Type is a system-defined delegate used to call methods without parameters.

private Thread myThread;
private void Form1_Load(object sender, EventArgs e)
{
  myThread = new Thread(new ThreadStart(RunsOnWorkerThread));
  ();
}
private void RunsOnWorkerThread()
{
  MethodInvoker mi = new MethodInvoker(SetControlsProp);
  BeginInvoke(mi);
}
private void SetControlsProp()
{
   = "myThread thread calls UI control";
}

2. Use directly (with parameters)

private Thread myThread;
private void Form1_Load(object sender, EventArgs e)
{
  myThread = new Thread(new ThreadStart(RunsOnWorkerThread));
  ();
}
private void RunsOnWorkerThread()
{
  //DoSomethingSlow();
  string pList = "myThread thread calls UI control";
  (new (UpdateUI), pList);
}
//Use it directly, there is no need to customize the delegationprivate void UpdateUI(object o,  e)
{
  // UI thread sets label1 attribute   = () + "success!";
}

3. Packaging

While the code in the second method solves this problem, it is quite cumbersome. If the helper thread wants to provide more feedback at the end, rather than simply giving a "Finished!" message, then the overly complex way of using BeginInvoke can be daunting. In order to convey other messages, such as "processing", "Everything goes well", etc., it is necessary to try to pass a parameter to the UpdateUI function. A progress bar may also be needed to improve feedback. So many calls to BeginInvoke can cause the helper thread to be dominated by that code. This will not only cause inconvenience, but also considering the coordination between the helper thread and the UI, this design is not good. After analyzing these, we believe that wrapping functions can solve these two problems.

private Thread myThread;
private void Form1_Load(object sender, EventArgs e)
{
  myThread = new Thread(new ThreadStart(RunsOnWorkerThread));
  ();
}
private void RunsOnWorkerThread()
{
  ////DoSomethingSlow();
  for (int i = 0; i < 100; i++)
  {
 ShowProgress( (i)+"%", i);
 (100);
  }
}
public void ShowProgress(string msg, int percentDone)
{
  // Wrap the parameters in some EventArgs-derived custom class:
   e = new MyProgressEvents(msg, percentDone);
  object[] pList = { this, e };
  BeginInvoke(new MyProgressEventsHandler(UpdateUI), pList);
}
private delegate void MyProgressEventsHandler(object sender, MyProgressEvents e);
private void UpdateUI(object sender, MyProgressEvents e)
{
   = ;
   = ;
}
public class MyProgressEvents : EventArgs
{
  public string Msg;
  public int PercentDone;
  public MyProgressEvents(string msg, int per)
  {
    Msg = msg;
    PercentDone = per;
  }
}

The ShowProgress method encapsulates the work that leads the call to the correct thread. This means that the helper thread code no longer has to worry about paying too much attention to UI details, and just call ShowProgress regularly.

If I provide a public method designed to be called from any thread, it's entirely possible that someone will call this method from the UI thread. In this case, there is no need to call BeginInvoke because I'm already in the correct thread. Calling Invoke is a total waste of time and resources, it is better to call the appropriate method directly. To avoid this, the Control class will expose a property called InvokeRequired. This is another exception to the UI thread only rule. It can be read from any thread, if the calling thread is a UI thread, it returns false, and other threads return true. This means I can modify the packaging as follows:

public void ShowProgress(string msg, int percentDone)
{
  if (InvokeRequired)
  {
 // As before
 //...
  }
  else
  {
 // We're already on the UI thread just
 // call straight through.
 UpdateUI(this, new MyProgressEvents(msg,PercentDone));
  }
}

Problem with thread opening form:

using System;
using ;
using ;
using ;
using ;
using ;
using ;
namespace WindowsApplication36
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();
    }
    private void Form1_Load(object sender, EventArgs e)
    {
    }
    private void button1_Click(object sender, EventArgs e)
    {
      new (new (invokeShow)).Start();
    }
    public void invokeShow()
    {
       Form f1 = new Form();
       (new (), new object[] { f1, null });
    }
    public void showForm(object sender,EventArgs e)
    {
      Form f1 = sender as Form;
      ();
    }
  }
}