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的時候,就可能出現 “在建立視窗控制代碼之前
這個解釋我感覺十分有道理。
給出的解決方法
解決的辦法就是讓執行緒等待,直到視窗控制代碼建立完畢:
//防止在視窗控制代碼初始化之前就走到下面的程式碼
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"。
解決辦法缺失如作者所說,去除已經被釋放物件的訂閱的事件即可。