毕设之c#多线程学习(官方+转载)
官方文档,原址:打开
如何:对 Windows 窗体控件进行线程安全调用
使用多线程提高 Windows 窗体应用程序的性能时,必须注意以线程安全方式调用控件。
示例
访问 Windows 窗体控件本质上不是线程安全的。如果有两个或多个线程操作某一控件的状态,则可能会迫使该控件进入一种不一致的状态。还可能出现其他与线程相关的 bug,包括争用情况和死锁。确保以线程安全方式访问控件非常重要。
.NET Framework 有助于在以非线程安全方式访问控件时检测到这一问题。在调试器中运行应用程序时,如果创建某控件的线程之外的其他线程试图调用该控件,则调试器会引发一个 InvalidOperationException,并提示消息:“从不是创建控件 control name 的线程访问它。”
此异常在调试期间和运行时的某些情况下可靠地发生。强烈建议您在显示此错误信息时修复此问题。在调试以 .NET Framework 2.0 版之前的 .NET Framework 编写的应用程序时,可能会出现此异常。
注意
可以通过将 CheckForIllegalCrossThreadCalls 属性的值设置为 false 来禁用此异常。这会使控件以与在 Visual Studio 2003 下相同的方式运行。
下面的代码示例演示如何从辅助线程以线程安全方式和非线程安全方式调用 Windows 窗体控件。它演示一种以非线程安全方式设置 TextBox 控件的Text 属性的方法,还演示两种以线程安全方式设置 Text 属性的方法。
1 using System; 2 using System.ComponentModel; 3 using System.Threading; 4 using System.Windows.Forms; 5 6 namespace CrossThreadDemo 7 { 8 public class Form1 : Form 9 { 10 // This delegate enables asynchronous calls for setting 11 // the text property on a TextBox control. 12 delegate void SetTextCallback(string text); 13 14 // This thread is used to demonstrate both thread-safe and 15 // unsafe ways to call a Windows Forms control. 16 private Thread demoThread = null; 17 18 // This BackgroundWorker is used to demonstrate the 19 // preferred way of performing asynchronous operations. 20 private BackgroundWorker backgroundWorker1; 21 22 private TextBox textBox1; 23 private Button setTextUnsafeBtn; 24 private Button setTextSafeBtn; 25 private Button setTextBackgroundWorkerBtn; 26 27 private System.ComponentModel.IContainer components = null; 28 29 public Form1() 30 { 31 InitializeComponent(); 32 } 33 34 protected override void Dispose(bool disposing) 35 { 36 if (disposing && (components != null)) 37 { 38 components.Dispose(); 39 } 40 base.Dispose(disposing); 41 } 42 43 // This event handler creates a thread that calls a 44 // Windows Forms control in an unsafe way. 45 private void setTextUnsafeBtn_Click( 46 object sender, 47 EventArgs e) 48 { 49 this.demoThread = 50 new Thread(new ThreadStart(this.ThreadProcUnsafe)); 51 52 this.demoThread.Start(); 53 } 54 55 // This method is executed on the worker thread and makes 56 // an unsafe call on the TextBox control. 57 private void ThreadProcUnsafe() 58 { 59 this.textBox1.Text = "This text was set unsafely."; 60 } 61 62 // This event handler creates a thread that calls a 63 // Windows Forms control in a thread-safe way. 64 private void setTextSafeBtn_Click( 65 object sender, 66 EventArgs e) 67 { 68 this.demoThread = 69 new Thread(new ThreadStart(this.ThreadProcSafe)); 70 71 this.demoThread.Start(); 72 } 73 74 // This method is executed on the worker thread and makes 75 // a thread-safe call on the TextBox control. 76 private void ThreadProcSafe() 77 { 78 this.SetText("This text was set safely."); 79 } 80 81 // This method demonstrates a pattern for making thread-safe 82 // calls on a Windows Forms control. 83 // 84 // If the calling thread is different from the thread that 85 // created the TextBox control, this method creates a 86 // SetTextCallback and calls itself asynchronously using the 87 // Invoke method. 88 // 89 // If the calling thread is the same as the thread that created 90 // the TextBox control, the Text property is set directly. 91 92 private void SetText(string text) 93 { 94 // InvokeRequired required compares the thread ID of the 95 // calling thread to the thread ID of the creating thread. 96 // If these threads are different, it returns true. 97 if (this.textBox1.InvokeRequired) 98 { 99 SetTextCallback d = new SetTextCallback(SetText); 100 this.Invoke(d, new object[] { text }); 101 } 102 else 103 { 104 this.textBox1.Text = text; 105 } 106 } 107 108 // This event handler starts the form's 109 // BackgroundWorker by calling RunWorkerAsync. 110 // 111 // The Text property of the TextBox control is set 112 // when the BackgroundWorker raises the RunWorkerCompleted 113 // event. 114 private void setTextBackgroundWorkerBtn_Click( 115 object sender, 116 EventArgs e) 117 { 118 this.backgroundWorker1.RunWorkerAsync(); 119 } 120 121 // This event handler sets the Text property of the TextBox 122 // control. It is called on the thread that created the 123 // TextBox control, so the call is thread-safe. 124 // 125 // BackgroundWorker is the preferred way to perform asynchronous 126 // operations. 127 128 private void backgroundWorker1_RunWorkerCompleted( 129 object sender, 130 RunWorkerCompletedEventArgs e) 131 { 132 this.textBox1.Text = 133 "This text was set safely by BackgroundWorker."; 134 } 135 136 #region Windows Form Designer generated code 137 138 private void InitializeComponent() 139 { 140 this.textBox1 = new System.Windows.Forms.TextBox(); 141 this.setTextUnsafeBtn = new System.Windows.Forms.Button(); 142 this.setTextSafeBtn = new System.Windows.Forms.Button(); 143 this.setTextBackgroundWorkerBtn = new System.Windows.Forms.Button(); 144 this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker(); 145 this.SuspendLayout(); 146 // 147 // textBox1 148 // 149 this.textBox1.Location = new System.Drawing.Point(12, 12); 150 this.textBox1.Name = "textBox1"; 151 this.textBox1.Size = new System.Drawing.Size(240, 20); 152 this.textBox1.TabIndex = 0; 153 // 154 // setTextUnsafeBtn 155 // 156 this.setTextUnsafeBtn.Location = new System.Drawing.Point(15, 55); 157 this.setTextUnsafeBtn.Name = "setTextUnsafeBtn"; 158 this.setTextUnsafeBtn.TabIndex = 1; 159 this.setTextUnsafeBtn.Text = "Unsafe Call"; 160 this.setTextUnsafeBtn.Click += new System.EventHandler(this.setTextUnsafeBtn_Click); 161 // 162 // setTextSafeBtn 163 // 164 this.setTextSafeBtn.Location = new System.Drawing.Point(96, 55); 165 this.setTextSafeBtn.Name = "setTextSafeBtn"; 166 this.setTextSafeBtn.TabIndex = 2; 167 this.setTextSafeBtn.Text = "Safe Call"; 168 this.setTextSafeBtn.Click += new System.EventHandler(this.setTextSafeBtn_Click); 169 // 170 // setTextBackgroundWorkerBtn 171 // 172 this.setTextBackgroundWorkerBtn.Location = new System.Drawing.Point(177, 55); 173 this.setTextBackgroundWorkerBtn.Name = "setTextBackgroundWorkerBtn"; 174 this.setTextBackgroundWorkerBtn.TabIndex = 3; 175 this.setTextBackgroundWorkerBtn.Text = "Safe BW Call"; 176 this.setTextBackgroundWorkerBtn.Click += new System.EventHandler(this.setTextBackgroundWorkerBtn_Click); 177 // 178 // backgroundWorker1 179 // 180 this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted); 181 // 182 // Form1 183 // 184 this.ClientSize = new System.Drawing.Size(268, 96); 185 this.Controls.Add(this.setTextBackgroundWorkerBtn); 186 this.Controls.Add(this.setTextSafeBtn); 187 this.Controls.Add(this.setTextUnsafeBtn); 188 this.Controls.Add(this.textBox1); 189 this.Name = "Form1"; 190 this.Text = "Form1"; 191 this.ResumeLayout(false); 192 this.PerformLayout(); 193 194 } 195 196 #endregion 197 198 199 [STAThread] 200 static void Main() 201 { 202 Application.EnableVisualStyles(); 203 Application.Run(new Form1()); 204 } 205 206 } 207 }
对 Windows 窗体控件的非线程安全调用
对 Windows 窗体控件的非线程安全调用方式是从辅助线程直接调用。调用应用程序时,调试器会引发一个 InvalidOperationException,警告对控件的调用不是线程安全的。
1 2 // This event handler creates a thread that calls a 3 // Windows Forms control in an unsafe way. 4 private void setTextUnsafeBtn_Click( 5 object sender, 6 EventArgs e) 7 { 8 this.demoThread = 9 new Thread(new ThreadStart(this.ThreadProcUnsafe)); 10 11 this.demoThread.Start(); 12 } 13 14 // This method is executed on the worker thread and makes 15 // an unsafe call on the TextBox control. 16 private void ThreadProcUnsafe() 17 { 18 this.textBox1.Text = "This text was set unsafely."; 19 }
对 Windows 窗体控件的线程安全调用
对 Windows 窗体控件进行线程安全调用
查询控件的 InvokeRequired 属性。
如果 InvokeRequired 返回 true,则使用实际调用控件的委托来调用 Invoke。
如果 InvokeRequired 返回 false,则直接调用控件。
在下面的代码示例中,此逻辑是在一个称为 SetText 的实用工具方法中实现的。名为 SetTextDelegate 的委托类型封装 SetText 方法。TextBox 控件的 InvokeRequired 返回 true 时,SetText 方法创建 SetTextDelegate 的一个实例,并调用窗体的 Invoke 方法。这使得 SetText 方法被创建TextBox 控件的线程调用,而且在此线程上下文中将直接设置 Text 属性。
1 // This event handler creates a thread that calls a 2 // Windows Forms control in a thread-safe way. 3 private void setTextSafeBtn_Click( 4 object sender, 5 EventArgs e) 6 { 7 this.demoThread = 8 new Thread(new ThreadStart(this.ThreadProcSafe)); 9 10 this.demoThread.Start(); 11 } 12 13 // This method is executed on the worker thread and makes 14 // a thread-safe call on the TextBox control. 15 private void ThreadProcSafe() 16 { 17 this.SetText("This text was set safely."); 18 } 19 20 C# 2122 23 // This method demonstrates a pattern for making thread-safe 24 // calls on a Windows Forms control. 25 // 26 // If the calling thread is different from the thread that 27 // created the TextBox control, this method creates a 28 // SetTextCallback and calls itself asynchronously using the 29 // Invoke method. 30 // 31 // If the calling thread is the same as the thread that created 32 // the TextBox control, the Text property is set directly. 33 34 private void SetText(string text) 35 { 36 // InvokeRequired required compares the thread ID of the 37 // calling thread to the thread ID of the creating thread. 38 // If these threads are different, it returns true. 39 if (this.textBox1.InvokeRequired) 40 { 41 SetTextCallback d = new SetTextCallback(SetText); 42 this.Invoke(d, new object[] { text }); 43 } 44 else 45 { 46 this.textBox1.Text = text; 47 } 48 }
使用 BackgroundWorker 进行的线程安全调用
在应用程序中实现多线程的首选方式是使用 BackgroundWorker 组件。BackgroundWorker 组件使用事件驱动模型实现多线程。辅助线程运行DoWork 事件处理程序,创建控件的线程运行 ProgressChanged 和 RunWorkerCompleted 事件处理程序。注意不要从 DoWork 事件处理程序调用您的任何控件。
下面的代码示例不异步执行任何工作,因此没有 DoWork 事件处理程序的实现。TextBox 控件的 Text 属性在 RunWorkerCompleted 事件处理程序中直接设置。
1 // This event handler starts the form's 2 // BackgroundWorker by calling RunWorkerAsync. 3 // 4 // The Text property of the TextBox control is set 5 // when the BackgroundWorker raises the RunWorkerCompleted 6 // event. 7 private void setTextBackgroundWorkerBtn_Click( 8 object sender, 9 EventArgs e) 10 { 11 this.backgroundWorker1.RunWorkerAsync(); 12 } 13 14 // This event handler sets the Text property of the TextBox 15 // control. It is called on the thread that created the 16 // TextBox control, so the call is thread-safe. 17 // 18 // BackgroundWorker is the preferred way to perform asynchronous 19 // operations. 20 21 private void backgroundWorker1_RunWorkerCompleted( 22 object sender, 23 RunWorkerCompletedEventArgs e) 24 { 25 this.textBox1.Text = 26 "This text was set safely by BackgroundWorker."; 27 }
转载Tank大牛,原文地址
在C# 的应用程序开发中, 我们经常要把UI线程和工作线程分开,防止界面停止响应。 同时我们又需要在工作线程中更新UI界面上的控件,
下面介绍几种常用的方法
线程间操作无效
界面上有一个button和一个label, 点击button会启动一个线程来更新Label的值
private void button1_Click(object sender, EventArgs e) { Thread thread1 = new Thread(new ParameterizedThreadStart(UpdateLabel)); thread1.Start("更新Label"); } private void UpdateLabel(object str) { this.label1.Text = str.ToString(); }运行后, 程序会报错 "跨线程操作无效,从不是创建"label1"的线程访问它"
这是因为.NET禁止了跨线程调用控件, 否则谁都可以操作控件,最后可能造成错误。
下面介绍几种跨线程调用控件的方法
第一种办法:禁止编译器对跨线程访问做检查
这是最简单的办法, 相当于不检查线程之间的冲突,允许各个线程随便乱搞,最后Lable1控件的值是什么就难以预料了 (不推荐使用这种方法)
public Form1() { InitializeComponent(); // 加入这行 Control.CheckForIllegalCrossThreadCalls = false; }第二种办法: 使用delegate和invoke来从其他线程中调用控件
调用控件的invoke方法,就可以控制控件了,例如
private void button2_Click(object sender, EventArgs e) { Thread thread1 = new Thread(new ParameterizedThreadStart(UpdateLabel2)); thread1.Start("更新Label"); } private void UpdateLabel2(object str) { if (label2.InvokeRequired) { // 当一个控件的InvokeRequired属性值为真时,说明有一个创建它以外的线程想访问它 Action<string> actionDelegate = (x) => { this.label2.Text = x.ToString(); }; // 或者 // Action<string> actionDelegate = delegate(string txt) { this.label2.Text = txt; }; this.label2.Invoke(actionDelegate, str); } else { this.label2.Text = str.ToString(); } }第三种办法: 使用delegate和BeginInvoke来从其他线程中控制控件
只要把上面的 this.label2.Invoke(actionDelegate, str); 中的 Invoke 改为BeginInvoke方法就可以了
Invoke方法和BeginInvoke方法的区别是
Invoke方法是同步的, 它会等待工作线程完成,
BeginInvoke方法是异步的, 它会另起一个线程去完成工作线程
第四种办法: 使用BackgroundWorker组件(推荐使用这个方法)
BackgroundWorker是.NET里面用来执行多线程任务的控件,它允许编程者在一个单独的线程上执行一些操作。耗时的操作(如下载和数据库事务)。用法简单
private void button4_Click(object sender, EventArgs e) { using (BackgroundWorker bw = new BackgroundWorker()) { bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted); bw.DoWork += new DoWorkEventHandler(bw_DoWork); bw.RunWorkerAsync("Tank"); } } void bw_DoWork(object sender, DoWorkEventArgs e) { // 这里是后台线程, 是在另一个线程上完成的 // 这里是真正做事的工作线程 // 可以在这里做一些费时的,复杂的操作 Thread.Sleep(5000); e.Result = e.Argument + "工作线程完成"; } void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { //这时后台线程已经完成,并返回了主线程,所以可以直接使用UI控件了 this.label4.Text = e.Result.ToString(); }
自学简单方法(毕业代码):1 private void btest_Click(object sender, EventArgs e) 2 { 3 lmessage.Text = "正在测试..."; 4 btest.Enabled = false; 5 tb = new TableBase(tserver.Text.Trim(), tname.Text.Trim(), tpsw.Text.Trim());//创建操作数据库类实例 6 t = new Thread(new ThreadStart(doWork ));//跨线程调用控件,方法三(推荐) 7 t.Start();//启动线程 8 tb.closed(); 9 10 } 11 delegate void SetControlCallback(string text,bool b); //声明一个委托(类似C函数指针) 12 void doWork()//子线程调用方法 13 { 14 SetControlCallback d = new SetControlCallback(setControl);//委托类型,参数必须为包含同参数的空方法 15 this.Invoke(d, new object[] { tb.open(),true }); 16 } 17 18 void setControl(String text,bool b)//设置控件方法 19 { 20 21 this.lmessage.Text = text; 22 this.btest.Enabled = b; 23 t.Abort(); t.Join(); 24 } 25 }