zoukankan      html  css  js  c++  java
  • c#中对"Crossthread operation not valid"错误的处理办法

    目录
     

     

    概要
    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似乎与目标背道而驰;第二,目前这种实现技术所带来的代码弹性的提升非常有限;不过有兴趣的,可以自己验证一下。
    参考
  • 相关阅读:
    解析HTTP协议六种请求方法
    金蝶
    普元
    中间件
    [CTSC2008] 网络管理
    【Uva 10498】满意值
    【SPOJ839】最优标号
    bzoj2879 [Noi2012]美食节
    bzoj3144 [Hnoi2013]切糕
    bzoj3112 [Zjoi2013]防守战线
  • 原文地址:https://www.cnblogs.com/snailrun/p/2644907.html
Copyright © 2011-2022 走看看