溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

C#中WinForm控件的跨線程更新,如何使用Invoke

發布時間:2020-07-16 11:14:01 來源:網絡 閱讀:5488 作者:btvnlue 欄目:編程語言

目的:

    用WinForm(C#)搭建一個用戶界面,一個進度條和一個按鈕,按鈕啟動進度條,進度完成時停止更新

示例:

C#中WinForm控件的跨線程更新,如何使用Invoke

    

實現:

    在按鈕事件中設置循環,更新進度條

        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();
            }
        }



附件:http://down.51cto.com/data/2367115
向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女