目的:
用WinForm(C#)搭建一個用戶界面,一個進度條和一個按鈕,按鈕啟動進度條,進度完成時停止更新
示例:
實現:
在按鈕事件中設置循環,更新進度條
private void btnProgress_Click(object sender, EventArgs e) { for (int ii = 0; ii < 100; ii++) { progressBar1.Value = ii + 1; Thread.Sleep(100); } }
問題1:
進度條的更新良好,但更新過程中窗口停滯,因為按鈕事件在窗口線程之中,執行在循環體中不會響應任何其他消息
解決1:
循環體中加入Application.DoEvents()
private void btnProgress_Click(object sender, EventArgs e) { for (int ii = 0; ii < 100; ii++) { Application.DoEvents(); progressBar1.Value = ii + 1; Thread.Sleep(100); } }
問題2:
這種方法可以使窗口事件共享線程時間資源,但各個執行片斷仍然是順序執行的,占時較長的執行片斷對其他事件影響顯著。
Thread.Sleep(100)可以假設成一段復雜邏輯或計算,如果將Sleep(100)改成Sleep(1000)或更大,用戶界面的效果便對問題1沒什么明顯改進。
解決2:
于是引入另外的線程,實現獨自對進度進行控制,實際上是對占時邏輯或計算的線程分離,使之不占用用戶界面的執行(時間)資源
public void threadProgress() { while (progressBar1.Value < 100) { progressBar1.Value++; Thread.Sleep(100); } } private void btnProgress_Click(object sender, EventArgs e) { Thread thprog = new Thread(threadProgress); thprog.Start(); }
這段代碼可以編譯,但無法執行,執行時會遇到InvalidOperationException,原因是WinForm控件的狀態更新只能在用戶界面線程內進行,實際上是創建窗體的線程,(和響應clicked事件的相同)。此時可以跟蹤Exception幫助文檔給出的解決方案,How to: Make Thread-Safe Calls to Windows Forms Controls描述完整的問題定義和解決辦法。
WinForm控件的跨線程更新要使用空間本身或線程內的Invoke方法,Invoke的參數要使用delegate函數指派。線程的行為需要修改。
public delegate void SetProgress(int pvv); void GuiSetProgressMustInInvokeOrUIThread(int pvv) { progressBar1.Value = pvv; } void DeleSetProgress(int pvv) { if (progressBar1.InvokeRequired) { SetProgress spself = new SetProgress(GuiSetProgressMustInInvokeOrUIThread); progressBar1.Invoke(spself, new object[] { pvv }); } } public void threadProgress() { while (progressBar1.Value < 100) { DeleSetProgress(progressBar1.Value + 1); Thread.Sleep(1000); } }
以上代碼是文檔中解決方案的拆解方法,DeleSetProgress確定為會被外部線程調用,它需要將實際的更新通過Invoke函數放回界面線程,這里使用delegate變量執行執行GuiSetProgressMustInInvokeOrUIThread。
幫助文檔中對Invoke的解釋是
Executes a delegate on the thread that owns the control's underlying window handle
從而保證在界面線程中的執行,當然執行片斷是GuiSetProgressMustInInvokeOrUIThread中的部分,將復雜邏輯與界面更新實現分離
問題3:
以上代碼會良好工作,但在更新過程中關閉窗口時,會遇到System.ObjectDisposedException。原因是窗口及空間被釋放后,用戶線程仍然在試圖更新進度條。
解決3:
幾個方案可供選擇,窗口關閉前強制結束用戶線程,窗口關閉前等待用戶邏輯結束,或更復雜的線程管理邏輯。
這里等待線程結束,線程則需要在成員中保留,用戶邏輯結束后設置標志,從而保證線程間調用的安全性。
private Thread thProgress = null; public void threadProgress() { ... thProgress = null; } private void FormProgress_FormClosing(object sender, FormClosingEventArgs e) { while (thProgress != null) { Application.DoEvents(); Thread.Sleep(0); } } private void btnProgress_Click(object sender, EventArgs e) { if (thProgress == null) { thProgress = new Thread(threadProgress); thProgress.Start(); } }
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。