control created on one thread cannot be modified

.Net技术 码拜 10年前 (2014-12-19) 2812次浏览 0个评论

Introduction 介绍

This article is to guide you how to create and handle threads in .Net. It has two sections; in the first section let us see about threading basics and how to create threads in .Net using C#. In the second section let us see how to handle threads in WinForms.
本文向您讲述如何在 .Net中创建和处理线程。共2节;第一节关于线程的基础和线程在C#中的应用。第二节关于如何处理Winform线程问题
1.1    Threading:-
1.1.1    Threading Basics:-
If you are familiar in threading basics you can skip this topic and continue with Threading in .Net.
A thread can be defined as a piece of code in execution. Each process has no less than one thread. So all the processes get executed in terms of thread. Any application will have an entry point called Main and at the maximum it can have only one Main. This is where an application starts. One who executes this Main function (i.e. starts the application) is called Main Thread. This main thread can spawn any number of threads depends upon the application requirement to execute the functionalities in parallel. Multiple threads in an application enable parallel execution. The term multiple threads signifies the total number of threads with in a single process not across the processes.
Multiple threads in execution is not similar to multiple process in execution. Each process has its own address space. Address space of one process cannot be used (read/write) by another process. Any communication between the processes should happen through the some IPC (Inter Process Communication) mechanisms. But the multiple threads in a process share the process address space. They don’t need any special mechanism for communication. They can communicate each other directly using a variable or some signals. Also context-switching (This how we achieve the parallel execution using a single processor!!!) is much time consuming to switch between the processes, but not in case of switching between threads. So multiple threads enables the parallel execution with in a process effectively.
Do I really need Multithreading?
Consider you have a calculator application which does some complex arithmetic operations. And also you need to provide a stop feature so that the user can stop the calculation at the middle before letting it to finish. Having only one thread in your application is not possible to address this requirement. You can not process the stop function while you are doing an arithmetic operation. As the application will be busy with executing the arithmetic operation it cannot get handle the stop until the arithmetic operation is completed. Here you need a thread which can execute the complex arithmetic operation and one (the main thread) will listen to the user inputs, here the stop. When the main thread receives the stop signal it can stop the thread spawned for execution of the arithmetic operation.
You have to be more careful when creating threads, because creating unwanted threads may reduce your application performance. Similarly avoiding threads in required places also distress your application.
1.1.2    Threading in .Net:-
If you are familiar with delegates you can skip this topic and continue with Creating Threads in .Net.
Before starting with the threads lets know what delegates are. A delegate can be defined as a data type which holds array of methods. All the methods inside a delegate should have the same signature and the same return type. The method signature and the return type of a delegate will be defined in the delegate declaration itself. C# syntax for delegate declaration is,
       public delegate void MyDelegate(int arg);
here the delegate MyDelegate can hold methods with the signature void function(int k)
To add a function into the delegate create the instance of the delegate and pass the function name as argument.
       MyDelegate delInstance = new MyDelegate(Function1);
Multiple functions can also be added in a delegate, to add more functions use += operator
       delInstance += new MyDelegate(Function2);
here the Funtion1 and Function2 have same signature as defined in the MyDelegate declaration. i.e. it should return void and take one parameter of type int.
Delegate can be invoked as normal functions just by passing the arguments.
       delInstance(5);
the above line of code will invoke all the functions inside the delInstance delegate sequentially. i.e. first Function1(5) will be called and then Function2(5). This is called multicasting delegates. i.e. a single invoke of the delegate instance intern calls multiple functions inside the delegate.
1.1.3    Creating Threads in .Net:-
Threads in .Net are created using the class “Thread”. This class is available in “System.Threading” namespace. To create a new thread you need to create an instance of the Thread class and apply Start method (available in the Thread class) on that instance. The Thread class constructor takes ThreadStart delegate as a parameter. (This ThreadStart is a delegate defined in System.Threading namespace) The signature of the ThreadStart delegate takes no arguments and returns void. So the function to be executed in a separate thread should return void and should not take any arguments.
C# syntax for creating threads,
       public static void main()
       {
              Thread t = new Thread(new ThreadStart(ClassName.MyFunction));
              t.Start();
              for(int i=0;i<100;i++)
                     Console.WriteLine(“I am in Main Thread {0}”,i);
       }
       public static void MyFunction()
       {
              for(int i=0;i<100;i++)
                     Console.WriteLine(“I am in Different Thread {0}”,i);
       }
In the above code we defined a function MyFunction which matches the ThreadStart delegate signature. Then we created an instance for the Thread class passing the ThreadStart delegate instance. Then we started the separate thread to execute the MyFunction while the same time main thread which spawned the MyFunction in a separate thread was also running.
On executing this we get the console messages swapped between main thread and different thread since both Main and MyFunction thread ‘for’ loops are running in parallel.
This is how we create threads in .Net. Here the Thread class can not take delegates of any signature as parameter, it just takes only the ThreadStart delegate type as parameter, and the ThreadStart delegate can take only the function having no argument and no return type.
Then what if you want to execute a function in thread which takes some argument? The answer for this question can be delegates.
All the delegates has got a inbuilt function called BeginInvoke() provided by the compiler. This function can be used for executing the function in a thread. BeginInvoke call on any delegate will be executed in a separate thread.
C# syntax follows,
       public void MyDelegate(int i, string str);
       public static void main()
       {
              MyDelegate delInstance = new MyDelegate (MyFunction);
              delInstance.BeginInvoke(100,” I am in Delegate Thread”, null, null);
              for(int i=0;i<100;i++)
                     Console.WriteLine(“I am in Main Thread {0}”,i);
       }
       public static void MyFunction(int count, string str)
       {
              for(int i=0;i<count;i++)
                     Console.WriteLine(str + “ {0}”,i);
       }
In the above code the BeginInvoke call on the delegate instance runs the MyFunction in a separate thread. The output of the above code will be, console messages swapped between main thread and the delegate thread (as similar as using Thread class). Note that multicasting a delegate through BeginInvoke is not possible.
Here our MyFunction returns no value. What if the function returns some value? How to collect the return value?
As BeginInvoke is an asynchronous call (just triggers the other thread and keep running the next line not waiting to finish the other thread) it cannot get the return value of the function call directly. So we need some mechanism to collect the return value. The implementation of BeginInvoke function in C# returns IAsyncResult type. This IAsyncResult can be used for getting the return value when required. There is a function called EndInvoke, which takes this IAsynResult as argument and collects the return value. So EndInvoke function can be used to collect the return value.
Every EndInvoke should be associated with a BeginInvoke as EndInvoke operates on the return value of BeginInvoke.
Return value can be collected with the following C# syntax,
public bool MyDelegate(int i, string str);
 public static void main()
 {
         MyDelegate delInstance = new MyDelegate (MyFunction);
         IAsyncResult ref = delInstance.BeginInvoke(100, ”I am in Delegate Thread”, null, null);
         for(int i=0;i<100;i++)
                Console.WriteLine(“I am in Main Thread {0}”,i);
         bool status = delInstance.EndInvoke(ref);
 }
       public static bool MyFunction(int count, string str)
       {
              for(int i=0;i<count;i++)
                     Console.WriteLine(str + “ {0}”,i);
              return true;
       }
In the above code call to the EndInvoke function takes the IsyncResult type returned by the BeginInvoke and returns value returned by the MyFunction. Thus the EndInvoke can be used to obtain the result of BeginInvoke.
Consider the following progam in C#,
public void MyDelegate(int i, string str);
       public static void main()
       {
              MyDelegate delInstance = new MyDelegate (MyFunction);
              delInstance.BeginInvoke(1000, ”I am in Delegate Thread”, null, null);
       }
       public static void MyFunction(int count, string str)
       {
              for(int i=0;i<count;i++)
                     Console.WriteLine(str + “ {0}”,i);
       }
if we look at the program closely, the main thread finishes its execution before the other thread is finished. When the main thread destroys all other threads automatically gets destroyed. So you may not get the desired output. To avoid this problem we can use EndInvoke. The EndInvoke function can also be used for blocking the current thread until the corresponding thread finishes its execution. A call to EndInvoke will block the current thread.
C# syntax uses EndInvoke to block the thread follows,
public void MyDelegate(int i, string str);
       public static void main()
       {
              MyDelegate delInstance = new MyDelegate (MyFunction);
              ISyncResult ref = delInstance.BeginInvoke(100,” I am in Delegate Thread”, null, null);
              delInstance.EndInvoke(ref);
       }
       public static void MyFunction(int count, string str)
       {
              for(int i=0;i<count;i++)
                     Console.WriteLine(str + “ {0}”,i);
       }
in the above code call to the EndInvoke blocks the main thread untill the MyFunction is completed.
A call to EndInvoke will collect the return value immediately if the thread has completed its execution already otherwise it will wait until the thread is completed and gets the result.
While making call to BeginInvoke we have not created any thread, then who is creating thread? How does it run in a separate thread?
To answer these question lets first know what thread pools are.
1.1.4    Thread Pool:-
Creating a thread at runtime is not an easy job. Most of the time will be spent on creating the thread rather than executing the functionality on the thread incase of non bigger threads.
So the CLR (Common Language Runtime) has some threads created already in a pool so that they can be used when ever required. These threads are called Thread Pool threads. If we are using a thread in the thread pool, on the completion of the thread the thread will not be destroyed. The thread will return to the thread pool in suspended state. This suspended thread can be used for some other purpose too.
The BeginInvoke function uses these Thread Pool threads to execute the functionalities. Usage of thread pool threads saves the thread creation time.
1.2    Threading in WinForms:-
In section1 we saw how to create a thread using Thread class and delegates. In this section let us see how to handle threads in WinForms. (This article assumes that you have worked on WinForms application).
1.2.1    WinForms Internals:-
Before starting with the threads directly lets understand how a windows application works. Consider you have a windows application where you create a text box and add the text box to the form. C# syntax follows
              Form1_Load(……)
{
    AddControl();
}
Void AddControl()
{
    TextBox textBox1 = new TextBox();
    Controls.add(textBox1);
}
On loading the form the AddControl function is invoked and that creates a text box and add it to the form using Controls.Add method.
The above code will run with out any error.
Consider the same application, where we would like to call the AddControl() method in a Thread. C# syntax follows
              Form1_Load(……)
{
    Thread t = new Thread(new ThreadStart(RunInThread));
    t.Start();
}
Void RunInThread()
{
    AddControl();
}
Void AddControl()
{
    TextBox textBox1 = new TextBox();
    controls.add(textBox1);
}
There is no change except we call the function AddControl from a separate thread (Not from the Main Thread). Here both the functions RunInThread and AddControl run in a same thread. If you try to run the above code, though our code compiles without any error we receive a run time error saying that “Controls created on one thread cannot be parented to a control on a different thread.” This is what we should worry about when dealing with threads in WinForms.
So why do we get this runtime error? A rule in windows is that “a control created on one thread cannot be modified in another thread”. In the second example the form was created in the main thread, and we are trying to modify the form object from another thread. That is a violation of windows rule (why do we have the rule is a different topic to explain). It is throwing the runtime error as we try to update the control from a different thread. We can simply say that any update on the UI should happen only through the main thread.
Then how to update the UI while we are in different thread? Before looking at the answer let us understand how windows application works.
All the windows applications will be having two queue structures, send message queue and post message queue. These queues will have the actions to be performed. The main thread is responsible for executing all the tasks there in the queue. The main thread will dequeue the messages one by one and executes the same.
Post Message Queue
Send Message Queue
Main Thread
The post message queue will have the messages posted through the PostMessage function. Similarly the send message queue will have the messages posted by SendMessage function. The difference between the SendMessage and the PostMessage is that the SendMessage will block the call until it is executed. But the PostMessage just put the message in the queue and returns back. The main thread first executes the send message queue messages and then it starts executing the post message queue.
So the answer for our question how to update the UI while we are in different thread is to put it in the message queue. Next question is how to Post/Send a message in .Net?
Answering this question is very simple. Control class has provided us three methods.
1.      Invoke
2.      BeginInvoke
3.      EndInvoke
Invoke method does SendMessage.
BeginInvoke method does PostMessage.
EndInvoke method used as part of BeginInvoke to obtain the result of the BeginInvoke call (As discussed in section1).
1.2.2    Control.Invoke:-
As mentioned earlier the Invoke method on Control class places the call into the send message queue. The call will ultimately be executed by the main thread and the Invoke method will block the current thread until the call is returned.
Invoke method on Control class has got two overloads.
       public System.Object Invoke ( System.Delegate method )
public virtual new System.Object Invoke ( System.Delegate method , object[] args )
the argument takes the delegate to be executed on the Main thread, if the parameters needs to be passed for the delegate it can be passed as object array. The return value can also be obtained as object.
Now let’s go back to the program,
              Public delegate void MyDelegate();
Form_Load(……)
{
    Thread t = new Thread(new ThreadStart(RunInThread));
    t.Start();
}
void RunInThread ()
{
MyDelagate delInstatnce = new MyDelagate(AddControl);
    this.Invoke(delInstatnce);
    MessageBox.Show(“Hello”);
// Add your code that needs to be executed in separate thread //except UI updation
}
void AddControl()
{
    TextBox textBox1 = new TextBox();
    Controls.add(textBox1);
}
Here we try to execute the AddControl function in a different thread using RunInThread function. The RunInThread function is executed in a separate thread. We try to call the AddControl function inside the RunInThread function through delegate. Though the RunInThread function run in a separate thread this.Invoke (Here the ‘this’ means the Form object) put the function to be executed in the send message queue. So the function is ultimately executed in the main thread in which the form was created. So it never throws an exception. (Note that, here AddControl and RunInThread functions are not executed in a same thread) The call to the Invoke function will block the current thread until the AddControl function is completed fully. i.e. The Message box will appear only after the complete execution of AddControl call.
1.2.3    Control.BeginInvoke:-
As seen earlier the BeginInvoke method on Control class places the call into the post message queue. This call will ultimately be executed by the main thread and the BeginInvoke method will not block the current thread as Invoke does. It will start executing the next line after placing the call in the post message queue. The return value can be collected using the EndInvoke method.
Similar to Invoke method BeginInvoke has got two overloads in Control class.
       public System.IAsyncResult BeginInvoke ( System.Delegate method )
       public virtual new System.IAsyncResult BeginInvoke ( System.Delegate method , object[] args )
arguments are similar to BeginInvoke, but the return type here is IAsynResult. This IAsynresult can be used for collecting the result using EndInvoke.
Public delegate void MyDelegate();
Form_Load(……)
{
    Thread t = new Thread(new ThreadStart(RunInThread));
    t.Start();
}
void RunInThread()
{
    MyDelagate delInstatnce = new MyDelagate(AddControl);
    this.BeginInvoke(delInstatnce);
    MessageBox.Show(“Hello”);
}
void AddControl()
{
    TextBox textBox1 = new TextBox();
    Controls.add(textBox1);
}
Since we have used the BeginInvoke here we could see the message box displayed before the AddControl is completed.
Consider the following C# syntax,
Public delegate void MyDelegate();
Form_Load(……)
{
    MyDelagate delInstatnce = new MyDelagate(AddControl);
    this.BeginInvoke(delInstatnce);
    for(int i=0; i<1000; i++)
           textBox1.Text += “Main ” + i.ToString();
}
void AddControl()
{
    for(int i=0; i<1000; i++)
           textBox1.Text += “Thread ” + i.ToString();
}
The above code will not run in parallel, since both the loops have to run in main thread. First it will print all the Form_Load ‘for’ loop messages and then it will print the AddControl for loop messages.
Consider the same C# syntax for Invoke call,
Public delegate void MyDelegate();
Form_Load(……)
{
    MyDelagate delInstatnce = new MyDelagate(AddControl);
    this.Invoke(delInstatnce);
    for(int i=0; i<1000; i++)
           textBox1.Text += “Main ” + i.ToString();
}
void AddControl()
{
    for(int i=0; i<1000; i++)
           textBox1.Text += “Thread ” + i.ToString();
}
The above code also will not run in parallel, since both the loops have to run in main thread. First it will print all the AddControl for loop messages and then it will print the Form_Load for loop messages. In reverse to the earlier one, as the Invoke method blocks the call.
1.2.4    Control.InvokeRequired:-
Consider the following C# syntax,
Form_Load(……)
{
    MyDelagate delInstatnce = new MyDelagate(AddControl);
    this.Invoke(delInstatnce);
}
void AddControl()
{
    TextBox textBox1 = new TextBox();
    Controls.add(textBox1);
}
In the above code we unnecessarily use the Invoke method though we are there in the main thread already. Here we know that we are at main thread and we can avoid using Invoke/BeginInvoke. But in some cases we don’t know whether the particular function will be executed by main thread or some other thread. And the caller don’t know whether the particular function have any UI update code inside. So the function which updates the UI should take care of taking the current thread to main thread. For that purpose we have a member “InvokeRequired” in the Control class which returns true if the current thread is not main thread, false if the current thread is main thread. So any update in the UI should happen through the InvokeRequired property as a safe coding. So the caller won’t handle the Invoke methods rather the implementation inside the function will handle.
C# syntax follows,
Form_Load(……)             //this function runs in main thread.
{
    AddControl();
}
void RunInThread()         //this function runs in a different thread.
{
AddControl();
}
void AddControl()
{
    if(this.InvokeRequired)
    {
           MyDelagate delInstatnce = new MyDelagate(AddControl);
           this.Invoke(delInstatnce);
           return;
}
    TextBox textBox1 = new TextBox();
    Controls.add(textBox1);
}
Here the caller just calls the function, but the function implementation take the current thread to main thread if required. In the above code Form_Load call to AddControl wont use the Invoke method but the RunInThread call to AddControl will use the delegate Invoke method.
BeginInvoke available in delegate and BeginInvoke available in Control class are not same. BeginInvoke on delegates uses the Thread Pool threads to execute the function in parallel. But the BeginInvoke in Control class just post a message in the messge queue. It will not create a separate thread to run the function. So the delegate BeginInvoke may enable parallelism but the Control BeginInvoke may not enable.

CodeBye 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权 , 转载请注明control created on one thread cannot be modified
喜欢 (0)
[1034331897@qq.com]
分享 (0)

文章评论已关闭!