1. 程式人生 > >C# Control的Invoke和BeginInvoke

C# Control的Invoke和BeginInvoke

之前在專案中遇到了UI介面更新出錯的問題,後來在網上找了很多資料,終於解決,先將資料整理如下:

為什麼需要Control.Invoke和Control.BeginInvoke??

如果從另外一個執行緒操作 windows窗體上的控制元件,就會和主執行緒產生競爭,造成不可預料的結果,甚至死鎖。因此 windows GUI程式設計有一個規則,就是隻能通過建立控制元件的執行緒來操作控制元件的資料,否則就可能產生不可預料的結果。為了方便地解決這些題, Control 類實現了 ISynchronizeInvoke 介面,提供了 Invoke  BeginInvoke 方法來提供讓其它執行緒更新 GUI 

介面控制元件的機制。

public interface ISynchronizeInvoke
{
        [HostProtection(SecurityAction.LinkDemand, Synchronization=true, ExternalThreading=true)]
        IAsyncResult BeginInvoke(Delegate method, object[] args);
        object EndInvoke(IAsyncResult result);
        object Invoke(Delegate method, object[] args);
        bool InvokeRequired { get; }
}

如果從執行緒外操作windows窗體控制元件,那麼就需要使用Invoke或者BeginInvoke方法,通過一個委託把呼叫封送到控制元件所屬的執行緒上執行。

Windows訊息機制是 windows平臺上的執行緒或者程序間通訊機制之一。 Windows訊息值其實就是定義的一個數據結構,最重要的是訊息的型別,它就是一個整數;然後就是訊息的引數。訊息的引數可以表示很多東西。

Windows 提供了一些 api 用來向一個執行緒的訊息佇列傳送訊息。因此,一個執行緒可以向另一個執行緒的訊息佇列傳送訊息從而告訴對方做什麼,這樣就完成了執行緒間的通訊。有些 api 傳送訊息需要一個視窗控制代碼,這種函式可以把訊息傳送到指定視窗的主執行緒訊息佇列;而有些則可以直接通過執行緒控制代碼,把訊息傳送到該執行緒訊息佇列中。

SendMessage windows api,用來把一個訊息傳送到一個視窗的訊息佇列。這個方法是個阻塞方法,也就是作業系統會確保訊息的確傳送到目的訊息佇列,並且該訊息被處理完畢以後,該函式才返回。返回之前,呼叫者將會被暫時阻塞。

PostMessage也是一個用來發送訊息到視窗訊息佇列的 api函式,但這個方法是非阻塞的。也就是它會馬上返回,而不管訊息是否真的傳送到目的地,也就是呼叫者不會被阻塞。

Invoke或者 BeginInvoke方法都需要一個委託物件作為引數。委託類似於回撥函式的地址,因此呼叫者通過這兩個方法就可以把需要呼叫的函式地址封送給介面執行緒。這些方法裡面如果包含了更改控制元件狀態的程式碼,那麼由於最終執行這個方法的是介面執行緒,從而避免了競爭條件,避免了不可預料的問題。如果其它執行緒直接操作介面執行緒所屬的控制元件,那麼將會產生競爭條件,造成不可預料的結果。

使用 Invoke完成一個委託方法的封送,就類似於使用 SendMessage方法來給介面執行緒傳送訊息,是一個同步方法。也就是說在 Invoke封送的方法被執行完畢前, Invoke方法不會返回,從而呼叫者執行緒將被阻塞。

使用 BeginInvoke方法封送一個委託方法,類似於使用 PostMessage進行通訊,這是一個非同步方法。也就是該方法封送完畢後馬上返回,不會等待委託方法的執行結束,呼叫者執行緒將不會被阻塞。但是呼叫者也可以使用 EndInvoke方法或者其它類似 WaitHandle機制等待非同步操作的完成。

但是在內部實現上, Invoke BeginInvoke都是用了 PostMessage方法,從而避免了 SendMessage帶來的問題。而 Invoke方法的同步阻塞是靠 WaitHandle機制來完成的。

那麼什麼時候使用Invoke,什麼時候使用BeginInvoke??

如果你的後臺執行緒在更新一個UI控制元件的狀態後不需要等待,而是要繼續往下處理,那麼你就應該使用BeginInvoke來進行非同步處理。

如果你的後臺執行緒需要操作UI控制元件,並且需要等到該操作執行完畢才能繼續執行,那麼你就應該使用Invoke。否則,在後臺執行緒和主截面執行緒共享某些狀態資料的情況下,如果不同步呼叫,而是各自繼續執行的話,可能會造成執行序列上的問題,雖然不發生死鎖,但是會出現不可預料的顯示結果或者資料處理錯誤。

可以看到ISynchronizeInvoke有一個屬性,InvokeRequired。這個屬性就是用來在程式設計的時候確定,一個物件訪問UI控制元件的時候是否需要使用Invoke或者BeginInvoke來進行封送。如果不需要那麼就可以直接更新。在呼叫者物件和UI物件同屬一個執行緒的時候這個屬性返回false。在後面的程式碼分析中我們可以看到,Control類對這一屬性的實現就是在判斷呼叫者和控制元件是否屬於同一個執行緒的。

 具體例項

我們用Thread來呼叫BeginInvokeInvoke

      我們開一個執行緒,讓執行緒執行一些耗費時間的操作,然後再用Control.InvokeControl.BeginInvoke回到使用者UI執行緒,執行介面更新。

 一、Thread呼叫Control的Invoke

    Private Delegate Sub InvokeDelegate()
    Private Sub StartMethod()
        'C程式碼段
        TextBox.Invoke(New InvokeDelegate(AddressOf InvokeMethod));
//上一句可能這樣寫更容易理解:
//InvokeDelegate FuncDelegate = new InvokeDelegate(InvokeMethod);
//TextBox.Invoke(FuncDelegate);
        'D程式碼段
    End Sub
 
    Private Sub InvokeMethod()
        'E程式碼段包含了處理TextBox的程式碼
    End Sub
 
    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        'A程式碼段
Dim InvokeThread As System.Threading.Thread = New Threading.Thread(AddressOf StartMethod)
        InvokeThread.Start()
        'B程式碼段
    End Sub

你覺得程式碼的執行順序是什麼呢?記好ControlInvokeBeginInvoke都執行在主執行緒即UI執行緒上

A------>(Start一開始BStartMethodC就同時執行)---->(C執行完了,不管B有沒有執行完,invokeThread把訊息封送(invoke)UI執行緒[Text],然後自己等待)---->UI執行緒處理完butInvoke_Click訊息後,處理invokeThread封送過來的訊息,執行invokeMethod方法,即程式碼段E,處理往後UI執行緒切換到invokeThread執行緒執行程式碼段D

這個Control.Invoke是相對於invokeThread執行緒同步阻塞的,阻止了其執行。

解釋:

1.UI執行A

2.UI開執行緒InvokeThreadBC同時執行,B執行線上程UI上,C執行線上程invokeThread上。

3.invokeThread封送訊息給UI,然後自己等待,UI處理完訊息後,處理invokeThread封送的訊息,即程式碼段E

4.UI執行完E後,轉到執行緒invokeThread上,invokeThread執行緒執行程式碼段D

 delegrate中的invokebegininvoke。 

 二、Thread呼叫ControlBeginInvoke

 Private Delegate Sub InvokeDelegate()
    Private Sub StartMethod()
        'C程式碼段
        TextBox.BeginInvoke(New InvokeDelegate(AddressOf InvokeMethod))
        'D程式碼段
    End Sub
 
    Private Sub InvokeMethod()
        'E程式碼段包含了處理TextBox的程式碼
    End Sub
 
    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        'A程式碼段
        Dim InvokeThread As System.Threading.Thread = New Threading.Thread(AddressOf StartMethod)
        InvokeThread.Start()
        'B程式碼段
    End Sub

這裡需要糾正一個誤區,那就是Control類上的非同步呼叫BeginInvoke並沒有開闢新的執行緒完成委託任務,而是讓介面控制元件的所屬執行緒完成委託任務的。看來非同步操作就是開闢新執行緒的說法不一定準確。 

你覺得程式碼的執行順序是什麼呢?記好ControlInvokeBeginInvoke都執行在主執行緒即UI執行緒上

AUI執行緒上執行----->beginInvokeThread執行緒開始執行,UI繼續執行程式碼段B,併發地invokeThread執行程式碼段C-------------->不管UI有沒有執行完程式碼段B,這時beginInvokeThread執行緒把訊息封送給UI,單自己並不等待,繼續向下執行-------->UI處理完Button2_Click訊息後,處理beginInvokeThread執行緒封送過來的訊息。

解釋:

1.UI執行A

2.UI開執行緒beginInvokeThreadBC同時執行,B執行線上程UI上,C執行線上程beginInvokeThread上。

3.beginInvokeThread封送訊息給UI,然後自己繼續執行程式碼DUI處理完訊息後,處理invokeThread封送的訊息,即程式碼段E

小結:

ControlBeginInvoke是相對於呼叫它的執行緒,即相對beginInvokeThread是非同步的。

因此,我們可以想到。如果要非同步取耗費長時間的資料,比如從資料庫中讀大量資料,我們應該這麼做。

ControlInvokeBeginInvoke的委託方法是在主執行緒,即UI執行緒上執行的。也就是說如果你的委託方法用來取花費時間長的資料,然後更新介面什麼的,千萬別在UI執行緒上呼叫Control.InvokeControl.BeginInvoke,因為這些是依然阻塞UI執行緒的,造成介面的假死。

你的委託方法只是用來更新介面的,因為其他執行緒不能安全的呼叫你的介面資料,所以用Control.InvokeBeginInvoke方法來將其他執行緒對於介面執行緒資料的操作封裝到一個委託傳到UI執行緒來執行,這樣就不會導致UI執行緒的阻塞造成介面的假死

那麼,這個非同步到底是什麼意思呢?

非同步是指相對於呼叫BeginInvoke的執行緒非同步,而不是相對於UI執行緒非同步,你在UI執行緒上呼叫BeginInvoke ,當然不行了。

相關推薦

C# InvokeBeginInvoke(1)

 近日,被Control的Invoke和BeginInvoke搞的頭大,就查了些相關的資料,整理如下。感謝這篇文章對我的理解Invoke和BeginInvoke的真正含義 。 (一)Control的Invoke和BeginInvoke 我們要基於以下認識: (1)Control的

c# InvokeBeginInvoke 區別

Control.Invoke 方法 (Delegate) :在擁有此控制元件的基礎視窗控制代碼的執行緒上執行指定的委託。 Control.BeginInvoke 方法 (Delegate) :在建立控制元件的基礎控制代碼所線上程上非同步執行指定委託。 (一)Control的

C# InvokeBeginInvoke理解

       在Invoke或者BeginInvoke的使用中無一例外地使用了委託Delegate,Invoke或者BeginInvoke方法都需要一個委託物件作為引數。委託類似於回撥函式的地址,因此呼叫者通過這兩個方法就可以把需要呼叫的函式地址封送給介面執行緒。這些方法裡面

C# 中的invokebegininvoke

原文地址:http://www.cnblogs.com/Z-King/archive/2011/11/03/2234337.html   1. control中的invoke、begininvoke。   2. delegrate中的invoke、begininvoke

C#中Invoke BeginInvoke的涵義區別

參考以下程式碼: public delegate void treeinvoke(); privatevoidUpdateTreeView() {          MessageBox.Show(System.Threading.Thread.CurrentThread.Name); } privat

C# Control的InvokeBeginInvoke

之前在專案中遇到了UI介面更新出錯的問題,後來在網上找了很多資料,終於解決,先將資料整理如下: 為什麼需要Control.Invoke和Control.BeginInvoke?? 如果從另外一個執行緒操作 windows窗體上的控制元件,就會和主執行緒產生競爭,造成不可

C# DataTable List之間相互轉換的方法

dbn execute 屬性 ins 集合 方法 summary efault getprop 一、List<T>/IEnumerable轉換到DataTable/DataView private DataTable ToDataTable<T>(

C#actionfunc的使用

進行 添加 col 數據庫 代碼 通過 需要 void oid 以前我都是通過定義一個delegate來寫委托的,但是最近看一些外國人寫的源碼都是用action和func方式來寫,當時感覺對這很陌生所以看起源碼也覺得陌生,所以我就花費時間來學習下這兩種方式,然後發

C# 裝箱拆箱[整理]

collect 調用 原理 本質 reger truct 以及 gree gre 1、 裝箱和拆箱是一個抽象的概念 2、 裝箱是將值類型轉換為引用類型 ;拆箱是將引用類型轉換為值類型 利用裝箱和拆箱功能,可通過允許值類型的任何值與O

C# JObjectJArray的使用

remove com ble jar lba cin p s ner syn ? 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 3

值得推薦的C/C++框架

its ++ sqli 解析生成 得到 types dns java 定時 Webbench是一個在linux下使用的非常簡單的網站壓測工具。它使用fork()模擬多個客戶端同時訪問我們設定的URL,測試網站在壓力下工作的性能,最多可以模擬3萬個並發連接去測試網站的負載

c++引用指針的徹底理解

內容操作 nbsp 容易 不必要 別名 影響 sof 的確 設置 ★ 相同點: 1. 都是地址的概念; 指針指向一塊內存,它的內容是所指內存的地址;引用是某塊內存的別名。 ★ 區別: 1. 指針是一個實體,而引用僅是個別名; 2.

銀行卡卡號識別:C#版本iOS版本

及其 emgucv ria topic ext 網址 整體 link 比較 (一)圖像采集 首先我們要取得待識別的圖像。這項工作可以通過數碼相機、DV機、工業攝像機、電腦數字攝像頭、手機攝像頭等設備采集,並從中取得我們要分析的圖像信息。 (二)版面分析

C# IQueryableIEnumerable的區別

機制 ide rep lan 處理機 about tools 數據庫 provider 原文地址:http://blog.csdn.net/q646926099/article/details/52297897 在使用EF查詢數據的時候,我們常用的查詢數據方式有linq t

InputStream只能讀取一次的解決辦法 C# byte[] Stream轉換

eof nbsp pos 讀取 處理 搜索 post ... 還要 x 情景--->>> 導入文件的時候,前臺傳過來一個文件, 後臺接到: HttpPostedFileBase file = Request.Files[0];由於對這個文件後臺處理

C#DateTimeRandom

大於 datetime minute 語法 tel new size second random 1.DateTime(日期) 定義: (1).DateTime 變量名 = new DateTime(年,月,日,時,分,秒);(年,月,日,時,分,秒的類型必須是int) (

C#錯誤異常的處理

類型 div 操作 包含 文本 出現 lap aps table 錯誤的出現並不總是編寫應用程序人的原因,有時應程序會因為應用程序的最終用戶引發或運行代碼的環境而發生錯誤。無論如何,我們都應預測應用程序中出現的錯誤,並相應的進行編碼。C#處理錯誤的機制可以為每種錯誤

C++ UTF8UTF16互轉代碼

define iter 般的 != ont for efault 互轉 小端 簡介 1、這段代碼只考慮在小端序情況下的轉換(一般的機器都是的)。2、這段代碼需要C++11的支持(只是用到了u16string),如果不支持,可以添加下面代碼 typedef uin

C#SpinWaitvolatile一點溫習

枚舉類型 代碼 針對 如果 mom spi 一段 volatile spa 今天看ConcurrentQueue<T> 源碼發現裏面居然沒有用到lock,我記得ConcurrentDictionary裏面是有lock的,lock的是字典裏面每一個key,但是Co

【學習筆記】C# 構造析構

成員 int 學習 pri [] func 釋放內存 ring 銷毀 構造方法 構造方法是一個特殊的方法,負責初始化對象 構造方法名必須和類名一致 構造方法沒有返回值,但可以有參數,能夠重載 構造方法可以不寫,系統會自動為類添加一個無參的默認構造 如果將構造方法設置為P