在編寫WinForm程序時,我們有很多時候需要用到進度條,下面我來分享一下我在處理進度條時所采用的各種方法。
創建一個Winform窗體應用項目。添加一個新的窗體(progressForm.cs)用來承載進度條;
在progressForm窗體添加一個進度條控件。
1,單線程進度條
向progressForm.cs添加一個方法Addprogess用來推動進度條。
public void Addprogess() { progressBar1.Value++; }
接下來便是在主窗體添加啟動的方法。先添加一個Boutton控件,用來啟動。
在按鈕單擊事件里添加以下代碼。
private void button1_Click(object sender, EventArgs e) { progressForm progress = new progressForm(); progress.Show(); for (int i = 0; i < 100; i++) { progress.Addprogess(); Thread.Sleep(50); } progress.Close(); }
用一個for循環模擬實際項目中需要循環插入數據或執行的方法,Thread.Seep(50)將線程掛起50毫秒模擬實際項目中每次操作需要的時間。
這樣一個進度條便完成了。在Winform里添加進度條是不是非常簡單呢。通過不超過10行的代碼,就能完成一個進度條。這就要感謝一下微軟把VisualStudio這個集成開發環境(integrateddevelopmentenvironment,IDE)的開發得這么強大。等明年VisualStudio 2019 出來后,宇宙第一IDE的稱號可就跑不了了。
在完成這個進度條后,雖然看起來是能正常運作,但是我們會發現一個問題:在進度條執行時,我們無法對主窗體進行操作,這也是很麻煩的一個問題。當然,在初學者階段很多人都不會去考慮這個問題。但是在我們工作后,完成一個程序的時需要考慮到的是程序的性能,易用性。這時我們就需要做一下改進。
2,多線程進度條
先普及一下多線程,
線程(Thread)是進程中的基本執行單元,是操作系統分配CPU時間的基本單位,一個進程可以包含若干個線程,在進程入口執行的第一個線程被視為這個進程的主線程。在.NET應用程序中,都是以Main()方法作為入口的,當調用此方法時系統就會自動創建一個主線程。線程主要是由CPU寄存器、調用棧和線程本地存儲器(Thread Local Storage,TLS)組成的。CPU寄存器主要記錄當前所執行線程的狀態,調用棧主要用於維護線程所調用到的內存與數據,TLS主要用於存放線程的狀態信息。
多線程的優點:可以同時完成多個任務;可以使程序的響應速度更快;可以讓占用大量處理時間的任務或當前沒有進行處理的任務定期將處理時間讓給別的任務;可以隨時停止任務;可以設置每個任務的優先級以優化程序性能。
那么可能有人會問:為什么可以多線程執行呢?總結起來有下面兩方面的原因:
1、CPU運行速度太快,硬件處理速度跟不上,所以操作系統進行分時間片管理。這樣,從宏觀角度來說是多線程並發的,因為CPU速度太快,察覺不到,看起來是同一時刻執行了不同的操作。但是從微觀角度來講,同一時刻只能有一個線程在處理。
2、目前電腦都是多核多CPU的,一個CPU在同一時刻只能運行一個線程,但是多個CPU在同一時刻就可以運行多個線程。
然而,多線程雖然有很多優點,但是也必須認識到多線程可能存在影響系統性能的不利方面,才能正確使用線程。不利方面主要有如下幾點:
(1)線程也是程序,所以線程需要占用內存,線程越多,占用內存也越多。
(2)多線程需要協調和管理,所以需要占用CPU時間以便跟蹤線程。
(3)線程之間對共享資源的訪問會相互影響,必須解決爭用共享資源的問題。
(4)線程太多會導致控制太復雜,最終可能造成很多程序缺陷。
當啟動一個可執行程序時,將創建一個主線程。在默認的情況下,C#程序具有一個線程,此線程執行程序中以Main方法開始和結束的代碼,Main()方法直接或間接執行的每一個命令都有默認線程(主線程)執行,當Main()方法返回時此線程也將終止。
一個進程可以創建一個或多個線程以執行與該進程關聯的部分程序代碼。在C#中,線程是使用Thread類處理的,該類在System.Threading命名空間中。使用Thread類創建線程時,只需要提供線程入口,線程入口告訴程序讓這個線程做什么。通過實例化一個Thread類的對象就可以創建一個線程。創建新的Thread對象時,將創建新的托管線程。Thread類接收一個ThreadStart委托或ParameterizedThreadStart委托的構造函數,該委托包裝了調用Start方法時由新線程調用的方法,示例代碼如下:
Thread thread=new Thread(new ThreadStart(method));//創建線程
thread.Start(); //啟動線程
上面代碼實例化了一個Thread對象,並指明將要調用的方法method(),然后啟動線程。ThreadStart委托中作為參數的方法不需要參數,並且沒有返回值。ParameterizedThreadStart委托一個對象作為參數,利用這個參數可以很方便地向線程傳遞參數,示例代碼如下:
Thread thread=new Thread(new ParameterizedThreadStart(method));//創建線程
thread.Start(3); //啟動線程
創建多線程的步驟:
1、編寫線程所要執行的方法
2、實例化Thread類,並傳入一個指向線程所要執行方法的委托。(這時線程已經產生,但還沒有運行)
3、調用Thread實例的Start方法,標記該線程可以被CPU執行了,但具體執行時間由CPU決定
(以上對多線程的解釋都引用於:https://www.cnblogs.com/dotnet261010/p/6159984.html)
看完上面的解釋對多線程多少也應該大致了解一點了,接下來開始修改Boutton點擊事件里的代碼。
private void button1_Click(object sender, EventArgs e) { Thread progressthread = new Thread(new ParameterizedThreadStart(thread)); progressthread.Start(); } public void thread(object length) { progressForm progress = new progressForm(); progress.Show(); for (int i = 0; i < 100; i++) { progress.Addprogess(); Thread.Sleep(50); }
progress.Close(); }
從代碼中可以看到,多添加了一個thread方法。在這個方法里放了原先放在button_Click里的代碼。
thread這個方法用來開辟新線程,將進度條放到了新線程里。修改成這樣后,運行發現在進度條加載時,還能同時對主窗體進行操作,避免了程序進入“假死”的狀態。這對於用戶來講是極大的提高了他們對程序的操作感受。
來到這里似乎沒什么問題了。在應用層上來講確實沒什么問題了,但是對於程序本身來講,我總覺得有那么一點問題。我個人覺得,主線程處理的業務不應放到進度條所在的線程。進度條所在的線程只需負責進度條就好了。主業務我們可以放到主線程或再開辟一條新的線程來處理。
3,使用委托和invoke方法跨線程UIl控制來實現進度條
這是進一步完善進度條。上面的進度條雖然用上了線程,但是當我們運行時就會發現鼠標到進度條窗口,總是變成加載狀態。這個我也不太清楚,估計和線程的阻塞有關。不管怎樣這個情況似乎嚴重不符合我們對程序的要求,畢竟看起來像bug。
我們改一下上面boutton控件的名稱。
Boutton:
Name:StartButton
添加一個lable控件(顯示文件處理的數量):
Name:DataNumber
代碼如下
using System; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace WindowsFormsApp8 { public partial class Form1 : Form { public delegate void addProgress();//創建委托 progressForm progress;//聲明一個progressForm窗口變量 public int n;//聲明一個變量顯示文件處理數量 public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { n = 0;//為變量n賦值,每次點擊按鈕都能初始化n的值(不初始化第二次開始時DataNumber.Text的值會從上一次結束時的值開始累加) progress = new progressForm();//實例化progressForm progress.Show();//顯示progressForm窗口實例 Thread progressthread = new Thread(new ParameterizedThreadStart(thread));//創建線程 progressthread.Start();//啟動線程 } public void thread(object length) { int temp = 1;//中間變量,實際數值等於進度條的值 for (int i = 0; i < 450; i++)//模擬使用環境處理450條信息 { if (i / 4.5 >temp)//進度條value最大值為100,要控制每處理4.5個數據調用一次方法使value自增1 { this.Invoke(new addProgress( progress.Addprogess));//invoke方法: 同步執行指定委托 temp++; } this.Invoke(new addProgress(DataNumberAdd));//同步執行委托 Thread.Sleep(50);//使線程掛起50毫秒,減緩進度條的變化 } this.Invoke(new addProgress(progress.Close));//同步執行委托 關閉進度條窗口 } /// <summary> /// DataNumber自增方法 /// </summary> public void DataNumberAdd() { n++; DataNumber.Text = n.ToString();//改變DataNumber的值 } } }
4,使用異步委托執行線程來完成進度條
異步委托不做多解釋,因為這個解釋起來並不是太容易,如果有心更進一步學習.Net,那么可以再網上搜索相對應的資料。
其實異步委托不適合做進度條,畢竟進度條是用來可視化反映數據加載或數據處理進度的,這一個同步反映的過程,異步委托完全不搭調。這里只做了解,如果真的實用的話這個不做考慮。
直接上代碼。
using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Windows.Forms; namespace TheardProgress { public partial class Form1 : Form { public List<int> Sum = new List<int>(); public delegate int addProgress(int i); public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { Thread progressthread = new Thread(new ParameterizedThreadStart(thread)); progressthread.Start(); label1.Text = Sum.Count().ToString(); } public void thread(object length) { bool a = true; progressForm progress = new progressForm(); progress.Show(); addProgress pro = add; for (int i = 0; i < 100; i++) { IAsyncResult asyncResult = pro.BeginInvoke(i, null, null); while (!asyncResult.IsCompleted) { Thread.Sleep(100); } //progress.Addprogess(); progress.labletxt(pro.EndInvoke(asyncResult)); } } public int add(int i) { Sum.Add(i); return Sum.Count(); } } }
通過IAsyncResult接口的IsComplete屬性來檢查委托是否完成了任務。EndInvote方法獲取執行結果。Sum模擬主業務的執行進度。這樣在進度條線程里創建一個IAsyncResul實例就可通過異步調用addProgress委托的實例pro,pro實現主業務,使用while(){}監控每一組業務是否完成,for循環表示所有業務循環完成。完成每一組業務,都會執行progress.labletxt()方法,更新進度條的進度。
在progressForm 添加labletxt()方法,
public void labletxt( int value) { progressBar1.Value = value; }
5,使用Async,await完成進度條(.NetFromawork4.5)
首先表述一下我對這兩個關鍵字理解:
Async表示異步,只要方法加上該關鍵字,那么每次執行該方法時,都會在一個新的子線程上運行該方法。
await表示等待,表示在該線程內,等待該關鍵字后面方法運行結束后才繼續執行后面的邏輯。
主窗體:
using System; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace WindowsFormsApp21 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } int Number = 0;//Number 表示完成的任務量,這個Number會直接傳到Form2(進度條承載窗口)作為進度條的Value public async void WorkAsync() { //async 關鍵字表示同步方法 Form2 progress = new Form2(); progress.Show(); for (int i = 0; i < 100; i++) { //等待每部分任務完成后,返回的結果 int num = await Task.Run(() => { //任務邏輯 Thread.Sleep(1000); return Number++;//完成任務后,Number自加 }); progress.SetProgerssBar(num);//設置進度條value } Number = 0; } private void button1_Click(object sender, EventArgs e) { WorkAsync(); } } }
進度條窗體:
using System.Windows.Forms; namespace WindowsFormsApp21 { public partial class Form2 : Form { public Form2() { InitializeComponent(); } public void SetProgerssBar(int Number) { progressBar1.Value = Number; } } }
這樣一個進度條就算完成了,但是即使如此,這個進度條依然存在許多缺陷等待完善。 其實小小的進度條,如果要做得好,依然需要不少的硬知識。學無止境。
z
