未从创建控件的线程访问解决办法

 

代码
概要
Windows Forms 控件通常不是thread
- safe(直接或间接继承于System.Windows.Forms.Control),因此.NET Framework为防止multithread下对控件的存取可能导致控件状态的不一致,在调试时,CLR - Debugger会抛出一个InvalidOperationException以‘建议‘程序员程序可能存在的风险。
 
问题的关键在于,动机是什么?和由此而来的编程模型的调整。
1 . Example
首先,看一个代码实例。该例要完成的工作是由一个Button的Click触发,启动一个Thread(Manual Thread),该Thread的目的是完成设置TextBox的Text’s Property。
1.1  Unsafe access to control
 
Code 
1.1
using  System;
using  System.Configuration;
using  System.Collections.Generic;
using  System.ComponentModel;
using  System.Data;
using  System.Drawing;
using  System.Text;
using  System.Windows.Forms;
using  System.Threading;
using  System.IO;
 
namespace  WindowsApplication1 {
    
public   partial   class  Form1 : Form {
        
public  Form1() {
            InitializeComponent();
        }
 
        
private   void  unsafeSetTextButton_Click( object  sender, EventArgs e) {
            Thread setTextThread 
=   new  Thread( new  ThreadStart(doWork));
            setTextThread.Start();
        }
 
        
private   void  doWork() {
            
string  fileName  =   " .\\test-src.txt " ;
            
if  ( ! File.Exists(fileName)) {
                MessageBox.Show(
string .Format( " {0} doesn't exist! " , fileName),
                    
" FileNoFoundException " );
                
return ;
            }
 
            
string  text  =   null ;
            
using  (StreamReader reader  =   new  StreamReader(fileName, Encoding.Default)) {
                text 
=  reader.ReadToEnd();
            }
 
            
this .textBox1.Text  =  text;
        }
    }
}
 
在调试时,CLR
- Debugger会在以上代码中粗体处将会弹出如下的对话框:

 
提示说,当前存取控件的thread非创建控件的thread(Main Thread)。
 
 
1.2  What’s mean ?
当然,你也可以忽略InvalidOperationException,在非调试的状态下,该异常并不会被抛出,CLR
- Debugger监测对Handle的可能存在的不一致地存取,而期望达到更稳健(robust)的代码,这也就是Cross - thread operation not valid后的真正动机。
 
但是,放在面前的选择有二:第一,在某些情况下,我们并不需要这种善意的‘建议‘,而这种建议将在调试时带来了不必要的麻烦;第二,顺应善意的‘建议‘,这也意味着我们必须调整已往行之有效且得心应手的编程模型(成本之一),而这种调整额外还会带来side
- effect,而这种side - effect目前,我并不知道有什么简洁优雅的解决之道予以消除(成本之二)。
 
2 . The first choice : CheckForIllegalCrossThreadCalls
忽略Cross
- thread InvalidOperationException建议,前提假设是我们不需要类似的建议,同时也不想给自己的调试带来过多的麻烦。
 
关闭CheckForIllegalCrossThreadCalls,这是Control class上的一个static property,默认值为flase,目的在于开关是否对Handle的可能存在的不一致存取的监测;且该项设置是具有Application scope的。
 
如果,只需要在某些Form中消除Cross
- thread InvalidOperationException建议,可以在Form的.ctor中,InitializeComponent语句后将CheckForIllegalCrossThreadCalls设置为false 。
 
Code 
2 -   1
public  Form1() {
    InitializeComponent();
 
    Control.CheckForIllegalCrossThreadCalls 
=   false ;
}
 
这种方式虽然可以达到忽略Cross
- thread InvalidOperationException建议的目的,但代码不能明晰的表达具有Application scope的语义,下面方式能更好的表达Application scope语义而且便于维护。
 
Code 
2 -   2
static   void  Main() {
    Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault( 
false  );
 
Control.CheckForIllegalCrossThreadCalls 
=   false ;
 
    Application.Run( 
new  Form1() );
}
 
 
3 . The second choice
接受Cross
- thread InvalidOperationException善意的建议,这通常是个明智的选择,即使目前没有简洁优雅的code pattern。
 
Code 
3 . –  1
using  System;
using  System.Configuration;
using  System.Collections.Generic;
using  System.ComponentModel;
using  System.Data;
using  System.Drawing;
using  System.Text;
using  System.Windows.Forms;
using  System.Threading;
using  System.IO;
 
namespace  WindowsApplication1 {
    
public   partial   class  Form1 : Form {
        
public  Form1() {
            InitializeComponent();
 
            
// Control.CheckForIllegalCrossThreadCalls = false;
        }
 
        
private   void  safeSetTextButton_Click( object  sender, EventArgs e) {
            Thread safeSetTextThread 
=   new  Thread( new  ThreadStart(doWork));
            safeSetTextThread.Start();
        }
 
        
private   void  doWork() {
            
string  fileName  =   " .\\test-src.txt " ;
            
if  ( ! File.Exists(fileName)) {
                MessageBox.Show(
string .Format( " {0} doesn't exist! " , fileName),
                    
" FileNoFoundException " );
                
return ;
            }
 
            
string  text  =   null ;
            
using  (StreamReader reader  =   new  StreamReader(fileName, Encoding.Default)) {
                text 
=  reader.ReadToEnd();
            }
 
            
// this.textBox1.Text = text;
            safeSetText(text);
        }
 
        
private   void  safeSetText( string  text) {
            
if  ( this .textBox1.InvokeRequired) {
                _SafeSetTextCall call 
=   delegate ( string  s) {
                    
this .textBox1.Text  =  s;
                };
 
                
this .textBox1.Invoke(call, text);
            }
            
else
                
this .textBox1.Text  =  text;
        }
 
        
private   delegate   void  _SafeSetTextCall( string  text);
    }
}
其中主要利用System.ComponentModel.IsynchronizeInvoke的InvokeRequired和Invoke 方法(System.Windows.Forms.Control继承于此),该code pattern对于大多数Windows控件有效 ;这样做的目的是保证由创建控件的Main Thread唯一性地呼叫get_Handle。(注意Code 
3 - 1  中的粗体 safeSetText方法)
 
但,System.Windows.Forms中ToolStripItem继承链上的控件并不具有后向兼容性,因此以上code pattern对此类控件不适用;可以将以上code pattern改为如下:
        
private   void  safeSetText( string  text) {
            
if  ( this .InvokeRequired) {
                _SafeSetTextCall call 
=   delegate ( string  s) {
                    
this .textBox1.Text  =  s;
                };
 
                
this .Invoke(call, text);
            }
            
else
                
this .textBox1.Text  =  text;
        }
 
        
private   delegate   void  _SafeSetTextCall( string  text);
 
因为System.Windows.Form继承System.Windows.Control,可以保证以上代码可以正确编译也能正常按期望工作,这样一来,代码的弹性会好些。
 
国外有兄弟利用Reflection技术将设置单一属性(Property)完全动态化了,代码的弹性因此也更好,但我不鼓励这种做法。理由有二:第一,之所以采用Multithread是因为需要更好的UI反应(interactive)、或者更好的性能、或者两者都要,在这种前提下,Reflection似乎与目标背道而驰;第二,目前这种实现技术所带来的代码弹性的提升非常有限;不过有兴趣的,可以自己验证一下。


本文来自CSDN博客,转载请标明出处:http:
// blog.csdn.net/leomaya/archive/2006/12/26/1463695.aspx

 

 

你可能感兴趣的:(线程)