1. 理解“Windows 窗體應用程序”項目中Program.cs文件中的main方法與傳統C++Console控制台程序中的main方法的區別。從程序運行層次上講,兩者無區別,都是程序的入口點,屬於進程中的第一個線程。前者隱藏了UI應用程序必需的消息循環,後者沒有。
2. 每個Windows桌面應用程序都必須包含至少一個UI線程,所謂UI線程,就是可以響應Windows消息的線程。通常情況下,除非特別需要,一個Windows桌面應用程序只包含一個UI線程。
3. UI線程本質上跟普通線程一樣,一般為程序的入口線程,比如Program.cs文件中的main方法,就是UI線程,而Application.Run()方法中封裝了消息循環。如果沒有Application.Run()方法,那麼它跟其他線程一模一樣。之所以叫做UI線程,是因為它之中包含一個類似
代碼如下:
While(GetMessage(…))//取Windows消息
{
//處理windows消息,調用開發者編寫的回調方法,如事件處理程序 等。
}
的循環。
4. 有關Windows消息機制等內容,請上網Google或者百度。
5. UI線程主要負責界面的實時更新,所以開發人員編寫代碼時,請遵守以下規律:
1) 不要在控件的事件處理程序中編寫(或者調用)耗時的代碼塊;
2) 不要在控件的事件處理程序中調用阻塞方法;
6. 明白程序設計中的 委托、事件、事件處理程序的區別
代碼如下:
1) Publicdelegate void KeyPressEventHandler(KeyPressEventArgse);
2) Public eventKeyPressEventHandler KeyPress;
3) Public void Textbox1_KeyPress(objectsender,KeyPressEventArgs e)
{
//….
}
其中:
1為委托 2為事件 3為事件處理程序
7. 所有的事件處理程序都是在UI線程中調用,又因為UI線程負責更新界面,所以UI線程始終必須保持順暢(表現為3中的while循環體不能耗時太長),即不能出現長時間執 行一個方法不返回的情況。所以,請遵守5中的規律。
8. 同一個方法,可以運行在多個線程之中,方法跟線程沒有一對一的原則
代碼如下:
Private void thread_pro() //
{
}
1) privatebutton1_click(object sender,EventArgs e)
{
thread_pro(); //thread_pro運行在UI線程中
}
2)private button1_click(object sender,EventArgs e)
{
Thread t = new Thread(newThreadStart(thread_pro));
Thread t1 = new Thread(new ThreadStart(thread_pro));
Thread t2 = newThread(new ThreadStart(thread_pro));
t.start(); //thread_pro運行在t線程中
t1.start(); //thread_pro運行在t1線程中
t.2.start(); //thread_pro運行在t2線程中
}
3) 還可以通過Control.Invoke() 或者BenginInvoke方法將方法投遞到創建該控件的線程中執行。
以上所有情況,請注意線程共享數據。
9. 多線程編程中,請注意“線程安全”問題,對於一些具備“非原子”操作的對象,必須采取措施避免發生錯誤。
UI控件(Button、datagridview等等)、集合(List、ArrayList)等屬於此類對象,控件任何時間都不能多線程訪問。
10. 堅決杜絕跨線程訪問UI控件,原因見9。跨線程訪問控件的方法見8中的3)。
11. 除了.Net Winform中的事件處理程序是在UI線程中調用以外,其它的回調方法幾乎所有都不會在UI線程中執行,所以,開發人員在編寫回調方法時,請遵守第9,10兩大規律。
12. 明白什麼叫回調方法。回調方法一般由開發者編寫,但不由開發者調用,由系統(或者說框架)調用。在Windows桌面應用程序開發過程中,控件的事件處理程序都屬於回調方法,回調方法一般用在“觀察者”設計模式中,當事件的激發者激發一個事件時,它就會調用回調方法。控件的所有事件都屬於此類。
另外一種常見為,異步執行某個操作,譬如,socket.BeginAccept()中的AsyncCallBack類型參數。
在框架橫行的時代,一般開發者編寫的代碼都屬於回調代碼。因為程序的主要結構都由先輩們在框架中集成好了。開發者們只需要像填空一樣完善空缺的部分。
13. 阻塞方法指,由於方法體內包含耗時較長的操作,所以方法不能及時返回。
所謂“及時”與“非及時”沒有絕對界限,示例如下:
代碼如下:
int func1() //及時返回
{
Int index = 0;
For(int i=0;i<100;i++)
{
Index ++;
}
Return index;
}
Int func2() //非及時返回
{
Int index = 0;
For(int i=0;i<1000;i++)
{
For(int j=0;j<1000;++j)
{
Index ++;
}
}
Return index;
}
上述func1相對而言,屬於非阻塞方法,func2屬於阻塞方法。
14. Windows窗體應用程序不會直接跟鍵盤、鼠標等硬件設備交互,它只與Windows消息有直接交互。雖然表面上鼠標鍵盤等硬件設備是操作在窗體之上的,但實質上,你 編寫的桌面應用程序是不會理解這些硬件設備的一舉一動。他們是通過操作系統(驅動程序)進行橋接的,操作系統先將硬件設備的一舉一動翻譯成windows消息(一種數據結構,程序可以理解),然後供程序理解,作出相應的反應。
15. 所謂“阻塞調用線程”,是指在某一個線程中調用了阻塞方法,從而使該線程不能及時執行以後的代碼。
代碼如下:
Void func()
{
Int index=0;
For(int i=0;i<10000;++i)
{
For(int j=0;j<10000;++j)
{
I ndex++;
}
}
}
Thread t = newThread(new ThreadStart(func));
t.Start(); //線程t中調用了阻塞方法func,因此線程t會被阻塞
在介紹func方法時,可以這樣描述:該方法會阻塞調用線程。
16. 同一個方法可以被多個線程調用,既可被UI線程調用,也可被非UI線程調用,那麼在方法體內怎麼編寫訪問UI控件(UI元素)的代碼呢?(跨線程訪問UI控件會引發異常)
代碼如下:
Void func()
{
Textbox1.Text=”測試”;
PictureBox1.Image = Image.FromFile(“a.jpg”);
}
1)以上func方法可能運行在UI線程中,如下:
代碼如下:
Private voidbutton1_Click(object sender,EventArgs e)
{
func(); //調用func方法
}
2)有如下,func方法可能運行在其他非UI線程中
代碼如下:
Private void button1_Click(object sender,EventArgs e)
{
Thread t = new Thread(newThreadStart(func));
t.Start(); //func訪問運行在t線程中
}
在2)中,可能引發異常。
以上問題的解決方案為:
修改func代碼為:
代碼如下:
Func()
{
If(this.InvokeRequired)
{
This.BeginInvoke((Action)delegate(){func()});
}
Else
{
Textbox1.Text=”測試”;
PictureBox1.Image = Image.FromFile(“a.jpg”);
}
}
有關BeginInvoke或者Invoke方法的使用,請上網Google或者百度。
17. 有關“跨線程訪問UI控件可能引發異常”的原因,跟多線程訪問集合可能出現錯誤的原因基本相似。下面列舉一段代碼說明情況
代碼如下:
ClassMyControl
{
Object root;
Public Draw()
{
GetRoot(root);
// 一系列操作…
ReleaseRoot(root);
}
Public OtherDraw()
{
GetRoot(root);
// 一系列操作 …
ReleaseRoot(root);
}
}
其中root變量同時只能被占用一次,GetRoot()獲取root的訪問權,如果root已經被占用,則拋出異常。ReleaseRoot()釋放root占用。
當在一個線程中(比如UI線程中)訪問MyControl類對象A,調用A.Draw()方法,執行到GetRoot(root)方法後,該線程失去控制權,暫停運行一下的代碼,即此時root已被占用。而另一線程中如果也要訪問同一對象A的Draw()方法,那麼就會引發異常。
18.在.Net Winform應用程序中,程序與用戶的交互主要包含兩個方面,一是用戶用鼠標、鍵盤燈硬件設備進行操作,程序響應操作,然後進行反饋(比如更新界面、刷新數據等),二是不需要用戶用鼠標等硬件設備進行操作,程序自己自動進行反饋(比如QQ彈出新聞窗體、360彈窗等)。
第一種情況是我們所熟知的,比如用戶用鼠標點擊按鈕(button1),程序則彈出一個MessageBox,我們在程序中是這樣子寫的:事件處理程序如下
代碼如下:
Private voidbutton1_Click(object sender,EventArgs e)
{
MessageBox.Show(“彈出對話框,或者其他操作”);
}
再來理一下這個過程,首先用戶拿起鼠標點擊button1,操作系統(鼠標驅動)會捕獲這個事件,經過分析,操作系統得知用戶點擊的是哪個窗體(按鈕)、點擊的位置(坐標),點擊類型(左鍵還是右鍵或者其他),以及其他信息,之後,將這些信息封裝成一個類型(即windows消息)發送給創建該窗體(控件)的線程中的消息隊列,之後,操作系統(鼠標驅動)就不在負責了。接著,UI線程從線程消息隊列中獲取該消息(
注意:這個過程是一直存在的),分析消息,調用開發人員編寫的一些回調方法,如button1_Click()方法,從而到達相應鼠標鍵盤操作的目的。從上面分析過程來看,再一次說明,程序是不會直接跟鼠標等硬件設備交互的,與它直接交互的只有Windows消息,而這個過程需要Windows操作系統起著重要作用。
第二種情況一般用在多線程編程中,當程序有耗時操作、或者需要一直監聽等情況的時候,是不能放在UI線程之中的,這時候就需要另外開辟線程,在另外的線程中處理。這種情況中,另外開辟的線程有時候需要反饋跟用戶一些信息,即更新UI界面或者彈出一個窗體等,這就涉及到跨線程訪問UI元素的問題了,詳見5和
19.以上代碼部分均為現寫的,可能出現拼寫錯誤,包涵!
另外,請配合筆記(二)中的“DOT NETWinform應用程序運行結構圖”閱讀。