一个简单的Form, 按钮btnTest是enabled=false。在btnEnable的Click事件中 创建线程,在线程中尝试设置btnTest.Enabled = true; 发生异常:线程间操作无效: 从不是创建控件“btnTest”的线程访问它。 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
1 using System; 2 using System.Threading; 3 using System.Windows.Forms; 4 5 namespace TestingUIThread 6 { 7 public partial class Form1 : Form 8 { 9 Thread thread = null; 10 11 public Form1() 12 { 13 InitializeComponent(); 14 } 15 16 private void btnEnable_Click(object sender, EventArgs e) 17 { 18 thread = new Thread(new ParameterizedThreadStart(EnableButton)); 19 thread.Start(null); 20 } 21 22 private void EnableButton(object o) 23 { 24 // following line cause exception, details as below: 25 //Cross-thread operation not valid: Control 'btnTest' accessed from a thread other than the thread it was created on. 26 btnTest.Enabled = true; 27 btnTest.Text = "Enabled"; 28 } 29 } 30 } |
在默认情况下,C#不准许在一个线程中直接访问或操作另一线程中创建的控件,这是因为访问windows窗体控件本质上是不安全的。 线程之间是可以同时运行的,那么如果有两个或多个线程同时操作某一控件的某状态,尝试将一个控件变为自己需要的状态时, 线程的死锁就可能发生。 为了区别是否是创建该控件的线程访问该控件,Windows窗体控件中的每个控件都有一个InvokeRequired属性,这个属性就是用来检查本控件是否被其他线程调用的属性,当被创建该线程外的线程调用的时候InvokeRequired就为true。有了这个属性我们就可以利用它来做判断了。 光判断出是否被其他线程调用是没有用的,所以windows窗体控件中还有一个Invoke方法可以帮我们完成其他线程对控件的调用。结合代理的使用就可以很好的完成我们的目标。 上面问题的解决方法: 方法一: public Form1() { InitializeComponent(); CheckForIllegalCrossThreadCalls = false; } // // Summary: // Gets or sets a value indicating whether to catch calls on the wrong thread // that access a control’s System.Windows.Forms.Control.Handle property when // an application is being debugged. // // Returns: // true if calls on the wrong thread are caught; otherwise, false. [EditorBrowsable(EditorBrowsableState.Advanced)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [SRDescription("ControlCheckForIllegalCrossThreadCalls")] [Browsable(false)] public static bool CheckForIllegalCrossThreadCalls { get; set; } 方法二:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
1 using System; 2 using System.Threading; 3 using System.Windows.Forms; 4 5 namespace TestingUIThread 6 { 7 public partial class Form1 : Form 8 { 9 Thread thread = null; 10 11 public Form1() 12 { 13 14 InitializeComponent(); 15 } 16 17 private void btnEnable_Click(object sender, EventArgs e) 18 { 19 thread = new Thread(new ParameterizedThreadStart(EnableButton)); 20 thread.Start(null); 21 } 22 23 private void EnableButton(object o) 24 { 25 EnableButton(); 26 } 27 28 private delegate void EnableButtonCallBack(); 29 30 private void EnableButton() 31 { 32 if (this.btnTest.InvokeRequired) 33 { 34 this.Invoke(new EnableButtonCallBack(EnableButton)); 35 } 36 else 37 { 38 btnTest.Enabled = true; 39 btnTest.Text = "Enabled"; 40 } 41 } 42 } 43 } |
方法三: 通过ISynchronizeInvoke和MethodInvoker.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
1 using System; 2 using System.ComponentModel; 3 using System.Threading; 4 using System.Windows.Forms; 5 6 namespace TestingUIThread 7 { 8 public partial class Form1 : Form 9 { 10 Thread thread = null; 11 12 public Form1() 13 { 14 InitializeComponent(); 15 } 16 17 private void btnEnable_Click(object sender, EventArgs e) 18 { 19 thread = new Thread(new ParameterizedThreadStart(MyEnableButton)); 20 thread.Start(null); 21 } 22 23 private void MyEnableButton(object sender) 24 { 25 ISynchronizeInvoke synchronizer = this; 26 27 if (synchronizer.InvokeRequired) 28 { 29 MethodInvoker minvoker = new MethodInvoker(this.EnableButton); 30 synchronizer.Invoke(minvoker, null); 31 } 32 else 33 { 34 this.EnableButton(); 35 } 36 } 37 38 private void EnableButton() 39 { 40 btnTest.Enabled = true; 41 btnTest.Text = "Enabled"; 42 } 43 } 44 } |
[…]
View DetailsC#程序的几种退出 1.Application.Exit(); //好像只在主线程可以起作用,而且当有线程,或是阻塞方法的情况下,很容易失灵 2.System.Environment.Exit(0); //无论在主线程和其它线程,只要执行了这句,都可以把程序结束干净 3.this.Close(); 4.Application.ExitThread(); msdn 对前台线程和后台线程的解释:托管线程或者是后台线程,或者是前台线程。后台线程不会使托管执行环境处于活动状态,除此之外,后台线程与前台线程是一样的。一旦所有前台线程在托管进程(其中 .exe 文件是托管程序集)中被停止,系统将停止所有后台线程并关闭。通过设置 Thread.IsBackground 属性,可以将一个线程指定为后台线程或前台线程。例如,通过将 Thread.IsBackground 设置为 true,就可以将线程指定为后台线程。同样,通过将 IsBackground 设置为 false,就可以将线程指定为前台线程。从非托管代码进入托管执行环境的所有线程都被标记为后台线程。通过创建并启动新的 Thread 对象而生成的所有线程都是前台线程。如果要创建希望用来侦听某些活动(如套接字连接)的前台线程,则应将 Thread.IsBackground 设置为 true,以便进程可以终止。 所以解决办法就是在主线程初始化的时候,设置: Thread.CurrentThread.IsBackground = true; 这样,主线程就是后台线程,在关闭主程序的时候就会关闭主线程,从而关闭所有线程。 但是这样的话,就会强制关闭所有正在执行的线程,所以在关闭的时候要对线程工作的结果保存。
View Details