有一次我給同事講述跨線程調用時使用了高速行駛的並行列車來比喻,感受比較形象。安全
多線程就像多個並行的列車,每一個線程在各自的軌道上不斷向前行駛。主界面所在的線程稱爲UI線程,也叫主線程,主線程依靠消息驅動,能夠將主線程的列車每節車箱想象爲一個消息,每次轉換並處理一個消息,處理過程當中若是有新的消息不會立刻處理而是放入一個消息隊列,等下一輪處理。markdown
例如我在屏幕上點擊一個按鈕,操做系統將鼠標的按下擡起等消息推送到消息隊列中。程序主線程的下一輪開始轉換這個消息而後處理這個消息,發送給指定窗口。假設咱們在點擊消息處理方法中進行一些界面更新,並調用了Invalidate,此時只是發出了消息,而後繼續執行後續代碼,當點擊消息處理完畢後,纔會從消息隊列獲取下一個消息處理。多線程
對於跨線程操做的Invoke,能夠這麼理解。就是並行列車在高速行駛中若是直接調用另外一個列車上的方法是很是危險的,咱們坐車的時候售票員老是提醒咱們不要把頭和手伸出窗外是一個道理。因此,假如咱們在一個主線程以外的線程列車上想要UI線程去執行一個方法,此時咱們須要將方法包裝成委託,而後經過Control.Invoke給主線程發送消息,主線程會在下一次消息處理時處理咱們的消息,由被調用的Control在UI線程執行咱們的方法。異步
若是咱們在Invoke後,還須要處理返回值,那麼咱們本身所在的列車就不能繼續開了,要停下列車,等主線程的列車處理完咱們的方法,返回結果,並經過消息發送回來,咱們收到返回的消息時,才繼續開動列車處理後續消息。也就是使用Invoke的返回的WaitHandle的WaitOne方法等待了。操作系統
須要理解Windows的消息驅動機制。咱們知道任意時刻執行的代碼必定是處於一個消息中,或者是空閒事件消息中。消息也是跨線程調用的基本機制。線程
Control.BeginInvoke是從線程池啓動一個線程執行,相對主線程是異步的。Control.Invoke則是在其餘線程中回到UI線程執行。但這兩種方式都不是推薦的最優作法,推薦用TPL模式,就是使用Task來進行異步。須要回到主線程時用AsyncOperation,原理是同樣的仍是發消息,只是AsyncOperation會發送給一個一定存在的句柄,避免線程安全問題。orm
另外一個不少人不明白的問題就是窗口句柄什麼時候建立,以及OnLoad的時機。其實,Winform程序是對本地代碼的包裝而已,底層仍是過程式語言的API調用。過程語言經過句柄來惟一標識全部的本地資源,全部的方法都須要傳入句柄 。而咱們建立的控件類其實並非真正的可見的類,翻看C++版本的代碼就能夠知道,其實仍是調用API來CreateWindow,此時傳入的類名纔是API中所指的類名,此時傳入的參數在Control裏使用了CreateParam結構體和CreateParam方法來實現。隊列
簡單的說吧,當咱們建立一個Button時,只是調用了Button的構造方法而已,並無在屏幕上可見,當咱們調用Parent的AddControl時,纔會去建立句柄,此時纔會觸發控件的OnCreateControl,若是控件是一個UserControl纔會觸發OnLoad事件。Control的OnCreateControl和UserControl的OnLoad是同一個時機發生的。只有建立了句柄纔會在屏幕上繪製出來,當父窗體隱藏時,全部子控件的句柄會銷燬,由於不用繪製了,而再次Show時,會從新建立句柄。事件