C# Winform 跨線程更新UI控制項常用方法總結(轉)

來源:互聯網
上載者:User

標籤:require   textbox   進度   body   ogr   ...   分享交流   對象   was   

出處:http://www.tuicool.com/articles/FNzURb

概述

C#Winform編程中,跨線程直接更新UI控制項的做法是不正確的,會時常出現“線程間操作無效: 從不是建立控制項的線程訪問它”的異常。處理跨線程更新Winform UI控制項常用的方法有4種: 
1. 通過UI線程的SynchronizationContext的Post/Send方法更新; 
2. 通過UI控制項的Invoke/BegainInvoke方法更新;

3. 通過BackgroundWorker取代Thread執行非同步作業; 
4. 通過設定表單內容,取消安全執行緒檢查來避免"跨線程操作異常"(非安全執行緒,建議不使用)。 
下文中對以上3種方法應用進行舉例說明,希望能對初識C# Winform的同學們有些協助。

成文表分享交流之意,惶恐水平有限,文中 理解和表述有錯誤之處還請大家多被批評指正。

本文

1.

用法: 

//共分三步        //第一步:擷取UI線程同步上下文(在表單建構函式或FormLoad事件中)        /// <summary>        /// UI線程的同步上下文        /// </summary>        SynchronizationContext m_SyncContext = null;        public Form1()        {            InitializeComponent();            //擷取UI線程同步上下文            m_SyncContext = SynchronizationContext.Current;            //Control.CheckForIllegalCrossThreadCalls = false;        }        //第二步:定義線程的主體方法        /// <summary>        /// 線程的主體方法        /// </summary>        private void ThreadProcSafePost()        {            //...執行線程任務            //線上程中更新UI(通過UI線程同步上下文m_SyncContext)            m_SyncContext.Post(SetTextSafePost, "This text was set safely by SynchronizationContext-Post.");            //...執行線程其他任務        }        //第三步:定義更新UI控制項的方法        /// <summary>        /// 更新文字框內容的方法        /// </summary>        /// <param name="text"></param>        private void SetTextSafePost(object text)        {            this.textBox1.Text = text.ToString();        }        //之後,啟動線程        /// <summary>        /// 啟動線程按鈕事件        /// </summary>        /// <param name="sender"></param>        /// <param name="e"></param>        private void setSafePostBtn_Click(object sender, EventArgs e)        {            this.demoThread = new Thread(new ThreadStart(this.ThreadProcSafePost));            this.demoThread.Start();        }

說明:三處加粗部分是關鍵。該方法的主要原理是:線上程執行過程中,需要更新到UI控制項上的資料不再直接更新,而是通過UI線程內容相關的Post/Send方法,將資料以非同步/同步訊息的形式發送到UI線程的訊息佇列;UI線程收到該訊息後,根據訊息是非同步訊息還是同步訊息來決定通過非同步/同步的方式調用SetTextSafePost方法直接更新自己的控制項了。

在本質上,向UI線程發送的訊息並是不簡單資料,而是一條委託調用命令。

// 線上程中更新UI(通過UI線程同步上下文m_SyncContext) 
m_SyncContext.Post ( SetTextSafePost , " This text was set safely by SynchronizationContext-Post. " ); 
可以這樣解讀這行代碼:向UI線程的同步上下文(m_SyncContext)中提交一個非同步訊息(UI線程,你收到訊息後以非同步方式執行委託,調用方法 SetTextSafePost,參數是“this text was ....”).

2.通過UI控制項的Invoke/BegainInvoke方法更新

用法:與方法1類似,可分為三個步驟。

// 共分三步        // 第一步:定義委託類型        // 將text更新的介面控制項的委託類型        delegate void SetTextCallback(string text);        //第二步:定義線程的主體方法        /// <summary>        /// 線程的主體方法        /// </summary>        private void ThreadProcSafe()        {            //...執行線程任務            //線上程中更新UI(通過控制項的.Invoke方法)            this.SetText("This text was set safely.");            //...執行線程其他任務        }        //第三步:定義更新UI控制項的方法        /// <summary>        /// 更新文字框內容的方法        /// </summary>        /// <param name="text"></param>        private void SetText(string text)        {            // InvokeRequired required compares the thread ID of the             // calling thread to the thread ID of the creating thread.             // If these threads are different, it returns true.             if (this.textBox1.InvokeRequired)//如果調用控制項的線程和建立建立控制項的線程不是同一個則為True            {                while (!this.textBox1.IsHandleCreated)                {                    //解決表單關閉時出現“訪問已釋放控制代碼“的異常                    if (this.textBox1.Disposing || this.textBox1.IsDisposed)                        return;                }                SetTextCallback d = new SetTextCallback(SetText);                this.textBox1.Invoke(d, new object[] { text });            }            else            {                this.textBox1.Text = text;            }        }        //之後,啟動線程        /// <summary>        /// 啟動線程按鈕事件        /// </summary>        /// <param name="sender"></param>        /// <param name="e"></param>        private void setTextSafeBtn_Click(            object sender,            EventArgs e)        {            this.demoThread =                new Thread(new ThreadStart(this.ThreadProcSafe));            this.demoThread.Start();        }

說明:這個方法是目前跨線程更新UI使用的主流方法,使用控制項的Invoke/BegainInvoke方法,將委託轉到UI線程上調用,實現安全執行緒的更新。原理與方法1類似,本質上還是把線程中要提交的訊息,通過控制項控制代碼調用委託交到UI線程中去處理。

解決表單關閉時出現“訪問已釋放控制代碼“的異常 部分代碼參考 部落格園-事理同學的文章 。

3.

用法:

//共分三步        //第一步:定義BackgroundWorker對象,並註冊事件(執行線程主體、執行UI更新事件)        private BackgroundWorker backgroundWorker1 =null;        public Form1()        {            InitializeComponent();                       backgroundWorker1 = new System.ComponentModel.BackgroundWorker();            //設定報告進度更新            backgroundWorker1.WorkerReportsProgress = true;            //註冊線程主體方法            backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);            //註冊更新UI方法            backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);            //backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);        }        //第二步:定義執行線程主體事件        //線程主體方法        public void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)        {            //...執行線程任務            //線上程中更新UI(通過ReportProgress方法)            backgroundWorker1.ReportProgress(50, "This text was set safely by BackgroundWorker.");            //...執行線程其他任務        }        //第三步:定義執行UI更新事件        //UI更新方法        public void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)        {            this.textBox1.Text = e.UserState.ToString();        }        //之後,啟動線程        //啟動backgroundWorker        private void setTextBackgroundWorkerBtn_Click(object sender, EventArgs e)        {            this.backgroundWorker1.RunWorkerAsync();        }

說明:C# Winform中執行非同步任務時,BackgroundWorker是個不錯的選擇。它是EAP(Event based Asynchronous Pattern)思想的產物,DoWork用來執行非同步任務,在任務執行過程中/執行完成後,我們可以通過ProgressChanged,ProgressCompleteded事件進行安全執行緒的UI更新。

需要注意的是: // 設定報告進度更新 
            backgroundWorker1.WorkerReportsProgress = true ; 
預設情況下BackgroundWorker是不報告進度的,需要顯示設定報告進度屬性。

4. 通過設定表單內容,取消安全執行緒檢查來避免"線程間操作無效異常"(非安全執行緒,建議不使用)

用法:將Control類的靜態屬性CheckForIllegalCrossThreadCalls為false。

public Form1()        {            InitializeComponent();            //指定不再捕獲對錯誤線程的調用            Control.CheckForIllegalCrossThreadCalls = false;        }

說明:通過設定CheckForIllegalCrossThreadCalls屬性,可以指示是否捕獲線程間非安全操作異常。該屬性值預設為ture,即線程間非安全操作是要捕獲異常的("線程間操作無效"異常)。通過設定該屬性為false簡單的屏蔽了該異常。Control.CheckForIllegalCrossThreadCalls的注釋如下

//        // 摘要:        //     擷取或設定一個值,該值指示是否捕獲對錯誤線程的調用,這些調用在調試應用程式時訪問控制項的 System.Windows.Forms.Control.Handle        //     屬性。        //        // 返回結果:        //     如果捕獲了對錯誤線程的調用,則為 true;否則為 false。        [EditorBrowsable(EditorBrowsableState.Advanced)]        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]        [SRDescription("ControlCheckForIllegalCrossThreadCalls")]        [Browsable(false)]        public static bool CheckForIllegalCrossThreadCalls { get; set; }


綜述:

文中介紹的4種方法,前三種是安全執行緒的 ,可在實際項目中因地制宜的使用。最後一種方法是非安全執行緒的,初學者可以實驗體會但不建議使用它。

下面列表對比一下這四種方法 

方法 安全執行緒 支援非同步 / 同步 其他
UI Sync Context 更新 Post/Send 盡量在表單建構函式、 FormLoad 中擷取同步上下文
控制項 Invoke control.Invoke/BegainInvoke 注意檢查控制項控制代碼是否已釋放
BackgroundWorker 更新

ProgressChanged、RunWorkerCompleted

事件同步更新
報告進度

CheckForIllegalCrossThreadCalls

取消跨線程調用檢查
同步更新 簡單,不建議使用

 

 

 

 

 

 

 

 

 

 

 

C# Winform 跨線程更新UI控制項常用方法總結(轉)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.