Because Windows Forms controls are not thread-safe in nature. Therefore, if two or more threads moderately operate the set value of a control, the control may be forced to enter an inconsistent state. Other thread-related bugs may also occur, including contention and deadlock. Run the application in the debugger.ProgramIf a thread other than the thread that creates a control tries to call the control, the debugger will throw an invalidoperationexception
This article uses a simple example to illustrate this problem (put a textbox and a button on the form, click the button, and set the textbox value in the new thread)
Solution 1: Disable the exception detection method to avoid exceptions.
Although this method avoids the exception throw, it cannot guarantee the correctness of the program running result (for example, when multiple threads set textbox1 text at the same time, it is difficult to predict what the final text of textbox1 is)
1 using system;
2 using system. Collections. Generic;
3 using system. componentmodel;
4 using system. Data;
5 using system. drawing;
6 using system. text;
7 using system. Windows. forms;
8 using system. Threading;
9
10 namespace winformtest
11 {
12 public partial class form1: Form
13 {
14 public form1 ()
15 {
16 initializecomponent ();
17 control. checkforillegalcrossthreadcils = false; // This row is critical.
18}
19
20
21 private void button#click (Object sender, eventargs E)
22 {
23 settextboxvalue ();
24}
25
26 void settextboxvalue ()
27 {
28 textboxsetvalue tbsv = new textboxsetvalue (this. textbox1, "Method1 ");
29 threadstart Ts = new threadstart (tbsv. settext );
30 thread t = new thread (TS );
31 T. Start ();
32}
33
34
35 class textboxsetvalue
36 {
37 private textbox _ textbox;
38 private string _ value;
39
40 public textboxsetvalue (textbox txtbox, string value)
41 {
42 _ textbox = txtbox;
43 _ value = value;
44}
45
46 Public void settext ()
47 {
48 _ textbox. Text = _ value;
49}
50}
51}
52}
Solution 2: Delegate security call
1 using system;
2 using system. Collections. Generic;
3 using system. componentmodel;
4 using system. Data;
5 using system. drawing;
6 using system. text;
7 using system. Windows. forms;
8
9 namespace winformtest
10 {
11 public partial class form2: Form
12 {
13 public form2 ()
14 {
15 initializecomponent ();
16}
17
18
19 private void button#click (Object sender, eventargs E)
20 {
21 settextboxvalue ();
22}
23
24
25 private delegate void callsettextvalue ();
26 // call by Delegate
27 void settextboxvalue ()
28 {
29 textboxsetvalue tbsv = new textboxsetvalue (this. textbox1, "method2 ");
30 if (tbsv. textbox. invokerequired)
31 {
32 callsettextvalue call = new callsettextvalue (tbsv. settext );
33 tbsv. textbox. Invoke (CALL );
34}
35 else
36 {
37 tbsv. settext ();
38}
39}
40
41
42 class textboxsetvalue
43 {
44 private textbox _ textbox;
45 private string _ value;
46
47 Public textboxsetvalue (textbox txtbox, string value)
48 {
49 _ textbox = txtbox;
50 _ value = value;
51}
52
53 public void settext ()
54 {
55 _ textbox. Text = _ value;
56}
57
58
59 Public textbox {
60 set {_ textbox = value ;}
61 get {return _ textbox ;}
62}
63}
64}
65}
Solution 3: Use the backgroundworker Control
1 using system;
2 using system. Collections. Generic;
3 using system. componentmodel;
4 using system. Data;
5 using system. drawing;
6 using system. text;
7 using system. Windows. forms;
8 using system. Threading;
9
10 namespace winformtest
11 {
12 public partial class form3: Form
13 {
14 public form3 ()
15 {
16 initializecomponent ();
17}
18
19 private void button#click (Object sender, eventargs E)
20 {
21 using (backgroundworker BW = new backgroundworker ())
22 {
23 BW. runworkercompleted + = settextboxvalue;
24 BW. runworkerasync ();
25}
26}
27
28 void settextboxvalue (Object sender, runworkercompletedeventargs E)
29 {
30 textboxsetvalue tbsv = new textboxsetvalue (this. textbox1, "method3 ");
31 tbsv. settext ();
32}
33
34
35 class textboxsetvalue
36 {
37 private textbox _ textbox;
38 private string _ value;
39
40 public textboxsetvalue (textbox txtbox, string value)
41 {
42 _ textbox = txtbox;
43 _ value = value;
44}
45
46 Public void settext ()
47 {
48 _ textbox. Text = _ value;
49}
50}
51
52}
53}
Users do not like slow response programs. It is wise to use multithreading when executing a time-consuming operation. It can improve the response speed of the program UI and make everything run faster. Multi-threaded programming in Windows was once the exclusive privilege of C ++ developers, but now it can be written in all languages compatible with Microsoft. NET.
However, the Windows form architecture imposes strict rules on thread usage. If you only write a single-threaded application, you do not need to know these rules because the single-threadedCodeIt is impossible to violate these rules. However, once multithreading is adopted, you need to understand the most important thread rule in Windows Forms: except for a few exceptions, otherwise, do not use any member of the control in a thread other than its creation thread. Exceptions of this rule are described in the document, but such exceptions are rare. This applies to any objects whose classes are derived from system. Windows. Forms. Control, including almost all elements in the UI. All the UI elements (including the form itself) are derived from the control class. In addition, 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 control bit that contains it. That is to say, all controls in a window belong to the same UI thread. In reality, most Windows Forms applications eventually have 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 methods on any controls on the user interface, unless it is indicated in the description of this method. There are few exceptions for this rule (there is always a document record) and there is little relationship between them. Note that the following code is invalid:
Private thread mythread;
Private void form1_load (Object sender, eventargs E)
{
Mythread = new thread (New threadstart (runsonworkerthread ));
Mythread. Start ();
}
Private void runsonworkerthread ()
{
Label1.text = "mythread thread calls UI control ";
}
If you try to run this code in. NET Framework 1.0, you may be lucky to run it, or it seems like this at first. This is the main problem in multithreading errors, that is, they are not immediately displayed. Even when some errors occur, everything looks normal before the first demo. But do not make a mistake-the code I just showed clearly violates the rules and can foresee any hope that "the trial run is good, there should be no problem. "people will pay a heavy price in the coming debugging period.
Next, let's take a look at the methods to solve this problem.
1. The system. Windows. Forms. methodinvoker 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 ));
Mythread. Start ();
}
Private void runsonworkerthread ()
{
Methodinvoker MI = new methodinvoker (setcontrolsprop );
Begininvoke (MI );
}
Private void setcontrolsprop ()
{
Label1.text = "mythread thread calls UI control ";
}
2. directly use system. eventhandle (with parameters)
Private thread mythread;
Private void form1_load (Object sender, eventargs E)
{
Mythread = new thread (New threadstart (runsonworkerthread ));
Mythread. Start ();
}
Private void runsonworkerthread ()
{
// Dosomethingslow ();
String plist = "mythread thread calls UI control ";
Label1.begininvoke (new system. eventhandler (updateui), plist );
}
// Directly use system. eventhandler, and there is no need to customize the delegate
Private void updateui (Object o, system. eventargs E)
{
// Set the label1 attribute in the UI thread
Label1.text = O. tostring () + "successful! ";
}
3. Packaging control. Invoke
Although the Code in the second method solves this problem, it is quite tedious. If the auxiliary thread wants to provide more feedback at the end, rather than simply giving "finished !" The begininvoke is too complex to use. To send other messages, such as "processing" and "everything goes smoothly", you need to try to pass a parameter to the updateui function. You may also need to add a progress bar to improve the feedback capability. So many calls to begininvoke may cause the auxiliary thread to be dominated by the Code. This will not only cause inconvenience, but also take into account the coordination between the auxiliary thread and the UI, this design is not good. After the analysis, we think that the packaging function can solve these two problems.
Private thread mythread;
Private void form1_load (Object sender, eventargs E)
{
Mythread = new thread (New threadstart (runsonworkerthread ));
Mythread. Start ();
}
Private void runsonworkerthread ()
{
//// Dosomethingslow ();
For (INT I = 0; I <100; I ++)
{
Showprogress (convert. tostring (I) + "%", I );
Thread. Sleep (100 );
}
}
Public void showprogress (string MSG, int percentdone)
{
// Wrap the parameters in some eventargs-derived custom class:
System. eventargs 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)
{
Lblstatus. Text = E. MSG;
Myprogresscontrol. value = E. percentdone;
}
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 directs the call to the correct thread. This means that the Helper thread Code does not need to pay too much attention to the UI details, but only needs to call showprogress regularly.
If I provide a public method designed to be called from any thread, it is entirely possible that someone will call this method from the UI thread. In this case, there is no need to call begininvoke because I am already in the correct thread. Calling invoke is a waste of time and resources. It is better to call appropriate methods directly. To avoid this, the control class exposes an attribute 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, false is returned, and other threads return true. This means that you 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 ));
}
}
Question about opening a form by a thread:
Using system;
Using system. Collections. Generic;
Using system. componentmodel;
Using system. Data;
Using system. drawing;
Using system. text;
Using system. Windows. forms;
Namespace windowsapplication36
{
Public partial class form1: Form
{
Public form1 ()
{
Initializecomponent ();
}
Private void form1_load (Object sender, eventargs E)
{
}
Private void button#click (Object sender, eventargs E)
{
New system. Threading. Thread (new system. Threading. threadstart (invokeshow). Start ();
}
Public void invokeshow ()
{
Form F1 = new form ();
This. Invoke (new system. eventhandler (this. showform), new object [] {F1, null });
}
Public void showform (Object sender, eventargs E)
{
Form F1 = sender as form;
F1.showdialog ();
}
}
}