1. 程式人生 > >C#中有關資源、BeginInvoke, Invoke和事件的事情

C#中有關資源、BeginInvoke, Invoke和事件的事情

 

事情是這麼來的,我開發的一個程式報了一個錯誤 “在建立視窗控制代碼之前,不能在控制元件上呼叫 Invoke 或 BeginInvoke錯誤”。

然後我在網上查資料,發現一個有意思的問題,文章出處為“在建立視窗控制代碼之前,不能在控制元件上呼叫 Invoke 或 BeginInvoke”錯誤。

 

問題

程式是如下這樣的。

Form1有Button1、Button2和Button3兩個按鈕,Button2是動態的new Form2窗體,Form2中註冊了Form1中的事件.

Button1的作用是關閉所有new出來的Form2窗體,並且把其資源釋放掉(Dispose()).

Button3是讓Form2中註冊Form1了的事件都發生。

操作:

先點Button2  new出幾個Form2的窗體,然後點選Button1釋放掉所有的資源,然後再單擊Button2 new出幾個Form2的窗體,再單擊Button3問題就出現了。老是提示"在建立視窗控制代碼之前,不能在控制元件上呼叫 Invoke 或 BeginInvoke"。

 

作者給出的解析

“在Window窗體程式開發的時候,如果使用多執行緒程式設計,在子執行緒中訪問主執行緒窗體內的控制元件,就需要使用控制元件的Control.Invoke方法或者BeginInvoke方法。但是有時候因為Window執行速度太快,尤其是你寫程式碼的時候在InitializeComponent();完成之前起了一個執行緒去執行某些操作,涉及到窗體控制元件的,當你在呼叫Control.Invoke的時候,就可能出現 “在建立視窗控制代碼之前

不能在控制元件上呼叫 Invoke 或 BeginInvoke”錯誤。

這個解釋我感覺十分有道理。

 

給出的解決方法

解決的辦法就是讓執行緒等待,直到視窗控制代碼建立完畢:
//防止在視窗控制代碼初始化之前就走到下面的程式碼
while (!this.IsHandleCreated)
{
    ;
}
this.BeginInvoke(new ProListIndexChangedDelegate(GetProLyric));

//根據不同情況也可以:
if (this.IsHandleCreated)
BeginInvoke(new ProListIndexChangedDelegate(GetProLyric));

 

其它人(鑽葛格)給的解決方法:

一個更巧妙的方法,只要在BeginInvoke方法的呼叫語句前再加一句:IntPtr i = this.Handle;就OK了,這比死迴圈配合this.IsHandleCreated的判斷方法更簡潔,因為this.Handle這個屬性本身就對應一個方法,取不到控制代碼,程式就不會向下進行。

如果以上方式時,IntPtr IsHandleCreated = this.Handle;報錯:“從不是建立他的執行緒訪問”。則可把this換成你想提取的控制代碼所在的執行緒中的窗體類(或其中任一控制元件)的例項名。

 本人也就是通過該方法最終解決問題的。也很有可能是作者給出的解釋的那種原因。

 

結果

作者的解決的結果

再次新建窗體的視窗控制代碼在Show()之後都通過IsHandleCreated屬性檢測過了,再呼叫Invoke()之前都建立了,還是會出錯。那問題出在哪裡呢?一開始還以為單擊Button1沒有真正的釋放資源,然後我就強制GC了,然後檢測不到以前的窗體了,但是還是不行,還是有問題。

然後就想到了“事件”上面來了。是不是事件的原因呀?以前建立的窗體沒有登出該事件,把之前所有的窗體都登出該事件,問題就解決了......

 

有留言認為作者的意思是:根本不是沒建立的原因,而是沒有清空事件。

我覺得確實應該是訪問了沒有建立的UI資源造成的!作者的解決方法是對的,但是說法是錯的。
也就是作者在Button1中釋放(Close)了Form2了。而卻沒有取消Form2中所訂閱的Form1的事件。
此時當點選了button3的話,將激發Form1中事件的委託鏈,而已經釋放掉的Form2物件的事件的響應,由於找到不到該物件資源了,造成了老是提示"在建立視窗控制代碼之前,不能在控制元件上呼叫 Invoke 或 BeginInvoke"。
解決辦法缺失如作者所說,去除已經被釋放物件的訂閱的事件即可。