二、UI線程和界面卡死


上回說到,在Windows窗體程序中,響應Windows消息的線程就被稱做Windows窗體程序的UI線程。UI線程還有一個重要的功能是創建和管理窗體和窗體中的各種控件,負責他們的實時刷新,如果UI線程在處理某個消息的時候耗時特別長,那么后續的消息就無法及時響應,看上去的感覺就是“界面卡死”了。此外,為了避免出現線程安全類的問題,UI控件是不能多線程訪問的,一個backgroundworker線程直接去刷新控件,這是絕對不允許的,但這種需求又是客觀存在的(比方說從數據庫中獲取數據后刷新到控件中)怎么辦呢?

 
.Net給出的解決方案如下:
1,對控件,實現ISynchronizeInvoke接口
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; }
}
 
2,對非UI線程,如果要更新UI線程,那么把自己的操作封裝到函數中,並聲明委托作為Invoke或者BeginInvoke方法的參數。委托類似於回調函數的地址,因此非UI線程通過這兩個方法就可以把需要調用的函數地址封送給界面線程。由於最終執行這個方法的是界面線程,從而避免了競爭條件,避免了不可預料的問題。
 
對非UI線程中調用Invoke或者BeginInvoke的區別在於,非UI線程調用Invoke后,非UI線程阻塞;而非UI線程調用BeginInvoke后,非UI線程不會阻塞。而不管是哪種調用,調用的委托方法都會在UI線程中執行。(在UI線程中調用控件的Invoke或者BeginInvoke方法是毫無意義的,仔細想想~~~)
 
使用Invoke完成一個委托方法的封送,就類似於使用SendMessage方法來給界面線程發送消息,是一個同步方法。也就是說在Invoke封送的方法被執行完畢前,Invoke方法不會返回,從而調用者線程將被阻塞。使用BeginInvoke方法封送一個委托方法,類似於使用PostMessage進行通信,這是一個異步方法。也就是該方法封送完畢后馬上返回,不會等待委托方法的執行結束,調用者線程將不會被阻塞。但是調用者也可以使用EndInvoke方法或者其它類似WaitHandle機制等待異步操作的完成。但是在內部實現上,Invoke和BeginInvoke都是用了PostMessage方法,從而避免了SendMessage帶來的問題。而Invoke方法的同步阻塞是靠WaitHandle機制來完成的。
 
如果你的后台線程在更新一個UI控件的狀態后不需要等待,而是要繼續往下處理,那么你就應該使用BeginInvoke來進行異步處理。如果你的后台線程需要操作UI控件,並且需要等到該操作執行完畢才能繼續執行,那么你就應該使用Invoke。否則,在后台線程和UI線程共享某些狀態數據的情況下,如果不同步調用,而是各自繼續執行的話,可能會造成執行序列上的問題,雖然不發生死鎖,但是會出現不可預料的顯示結果或者數據處理錯誤。
 
回到標題上,Windows窗體程序如何避免界面卡死呢?
1,負責與用戶交互的線程(以下簡稱為UI線程)應該保持順暢,當UI線程調用的API可能引起阻塞時間超過30毫秒時(比如訪問CD-ROM等速度超慢的外設、進行遠程調用等等)就應該考慮使用多線程。對UI線程而言實際上就是:1、發出調用,2、立刻返回。
2,在Windows Form中使用多線程時,除了創建控件的線程以外,絕對不要在任何其他線程里面直接調用控件的成員,如果需要,請使用invoke或者BeginInvoke。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM