返回列表 回復 發帖

[教學]超耗時迴圈暫停/繼續功能

遇上很耗時的計算,會不會有那種想要暫停一下後,等有時間時再繼續執行的念頭呢?

先來說說何謂耗時。

每個人能忍受電腦處理的速度都不一樣,多半都嘛是愈省時愈好,簡單說,就是希望點下去之後不用等待,資料就統統處理好的那種。不可否認,我也是屬於這類人,當然也還沒有科幻到電腦的處理快過光速,那可真是人類科技的超級大進步。除此之外,也有那種需要跟著正常時間執行的程式,好比說播放音樂、影片,總不會一點下去,兩個小時的內容全部播放完畢,給誰看啊?不過這個並不算是本主題要研究的範圍,不提。

既然慢得有理,咱們只能慢慢等待電腦處理完畢,但是問題來啦,要是跑某些運算時得花上好幾天,那豈不是電腦都不能關機,得保持開機狀態?也許保持開機狀況還能算是事小,萬一遇上當機那怎麼辦?難道沒有辦法解決?

於是乎,如何設計出可以暫停,又能繼續接著執行的程式,就是一門值得深入研究的技巧了。

需要恢復暫停時的狀態後繼續處理,也就意味者程式必須知道執行到了哪裡,所以必須安排些執行指標、記錄等,好在恢復執行時可以接著最後一次中斷時的工作下去,故,這方面會用到的資料請仔細規劃,好方便設計程式。

實作:

由於需要保存各項資料處理進度,這裡統一使用結構來封裝資料,方便直接讀/寫進度資料,不必做繁複的資料轉換功能,唯一的缺點是嚴禁使用非固定長度 String 型態的變數
  1. ' 兩個 Command , Name = Command1 ; Name = Command2 。

  2. Private Type 保存器
  3.     i As Long       ' 記錄迴圈用
  4.     Count As Long   ' 記錄執行狀況
  5.     ' ...           ' 若有其他需要保的資料在此追加, 禁用 String 型態變數
  6. End Type

  7. Dim regType As 保存器
  8. Dim 中斷旗號 As Boolean
  9. Dim RunPath As String

  10. Private Sub Form_Load()
  11. RunPath = Get_工作路徑("")
  12. Command1.Enabled = True
  13. Command2.Enabled = False
  14. End Sub

  15. Private Sub Command1_Click()
  16. 中斷旗號 = False
  17. Command1.Enabled = False
  18. Command2.Enabled = True
  19. DoEvents
  20. 耗時1
  21. Command1.Enabled = True
  22. Command2.Enabled = False
  23. End Sub

  24. Private Sub Command2_Click()
  25. 中斷旗號 = True
  26. End Sub

  27. Sub 耗時1()
  28. Dim i As Long, j As Long
  29. Dim Count As Long
  30. Dim FileNum As Integer, FileNum2 As Integer
  31. Dim 檔名 As String, 進度檔 As String

  32. 進度檔 = RunPath & "進度.set"
  33. 檔名 = RunPath & "TestFile1.txt"

  34. FileNum = FreeFile
  35. ' 檢查檔案是否存在
  36. If Len(Dir(進度檔, vbArchive Or vbHidden Or vbNormal Or vbReadOnly)) = 0 Then
  37.     ' 檔案不存在則重設起始位置
  38.     regType.i = 0
  39.     regType.Count = 0
  40.    
  41.     Open 檔名 For Output As FileNum     ' 重設資料檔
  42. Else
  43.     ' 取得最後中斷位置
  44.     Open 進度檔 For Binary As FileNum
  45.     Get #FileNum, , regType
  46.     Close #FileNum
  47.    
  48.     Open 檔名 For Append As FileNum     ' 追加資料
  49. End If

  50. Count = regType.Count
  51. For i = regType.i To 10000000
  52.     If i Mod 100 = 0 Then
  53.         DoEvents    ' 取得中斷狀態
  54.         If 中斷旗號 Then
  55.             ' 記錄執行狀態與結果
  56.             regType.i = i
  57.             regType.Count = Count
  58.             
  59.             FileNum2 = FreeFile
  60.             Open 進度檔 For Binary As FileNum2
  61.             Put #FileNum2, , regType
  62.             Close #FileNum2
  63.             Exit For    ' 強制結束迴圈
  64.         End If
  65.     End If
  66.    
  67.     ' 處理耗時迴圈內的演算內容...
  68.     If i Mod 2 = 0 Then
  69.         Count = Count + i
  70.     Else
  71.         Count = Count - i
  72.     End If
  73.    
  74.     Print #FileNum, Count       ' 資料輸出
  75. Next
  76. Close #FileNum
  77. End Sub
複製代碼
結論:

這個技巧對多數人來說也許冷門,尤其是執行較為龐大計算時,手邊也只有那麼一台電腦要用,偏偏同一台電腦上又要做多工時,這個技巧的實用性也就相對增加了許多。

當然啦,萬一遇上需要停機時,好比說接到了停電通知之類的不可抗拒的意外狀況時,就可以隨時依照需要適時的中斷,待結束停電後繼續計算,不用重新花時間去處理已經執行過的進度。

有時,應用這種技巧未必是好事,如果能在耗時的演算法這方面設法增加運算效能與程式碼優化,也是必須進修學習自我強加的。

本技巧的延申用法,可以協助達成「自動保存」完成進度的功能,不怕遇上當機或斷電的突發狀況。

在耗時迴圈中,只被允許保存最外層迴圈處理次數,此理由是受到迴圈規則限制,無法直接跳回內層迴圈執行。當然,也不是沒有解法,把迴圈改由 GoTo  來做即可,相對的,程式碼的嚴謹程度就受到破壞,亦不容易撰寫與閱讀。

本文同步刊載於:http://tw.myblog.yahoo.com/shege-1975/article?mid=2210

Get_工作路徑() 函數請參閱:http://tw.myblog.yahoo.com/shege-1975/article?mid=584
1

評分次數

超耗時迴圈暫停/繼續功能(二)

話說前一個範例還不夠完美,因為執行完程式後並沒有做資源回收的工作,也就是說,並沒有加上刪除中斷時為了記錄執行進度用的進度檔。

前篇範例簡單的流程圖請參閱底下的圖一,有助於大家對流程的熟悉與了解。



當然,程式碼還能額外增加一些防呆功能,好比說整個需要演算的東西都已經處理完畢結束,就不必重新再跑一次,尤其是萬一不小心再執行到,那麼又要再花時間耗時了,做做這樣子的防呆也是必要的,除非想再驗證一下演算是否錯誤,那麼也得讓程式聰明點,至少別把已經處理好的輸出檔案給刪掉,得小心手誤而做了超級大白工。

除了前一篇文章提到的自動存檔,這裡可分成兩個方向來研究,一種是計時、另一種是計量。

差別差在哪兒呢?講起來也有點複雜,在固定的執行時間裡保存一次,這方法得配合計時器或是時鐘來應用,時間檢查可能會多了點;固定執行次數的方法相對就簡單了些,只要執行次數符合等差數列就保存一次,缺點就很明顯,每執行一次耗時多久就成了頭痛問題,公差的大小多少都會影響到執行效能。

不管用哪種,總需要在迴圈裡放個 DoEvents 指令讓系統來處理一下各種視窗的事件,但也曾經提過,過份使用 DoEvents 指令也會造成迴圈執行效能低落,請參閱:別濫用 DoEvents 函數別濫用 DoEvents 函數(二)


改良版本的可自動儲存、中斷/恢復範例:
  1. ' 請把上一章節 Command1_Click() 內容中呼叫「耗時1」改為「耗時2」
  2. ' 改寫「耗時1」後的原始碼

  3. Sub 耗時2()
  4. Dim i As Long, j As Long
  5. Dim Count As Long
  6. Dim FileNum As Integer, FileNum2 As Integer
  7. Dim 檔名 As String, 進度檔 As String

  8. Dim 迴圈上限 As Long

  9. 迴圈上限 = 10000000

  10. 進度檔 = RunPath & "進度.set"
  11. 檔名 = RunPath & "TestFile1.txt"

  12. FileNum = FreeFile
  13. ' 檢查檔案是否存在
  14. If Len(Dir(進度檔, vbArchive Or vbHidden Or vbNormal Or vbReadOnly)) = 0 Then
  15.     If Len(Dir(檔名, vbArchive Or vbHidden Or vbNormal Or vbReadOnly)) <> 0 Then
  16.         If MsgBox(檔名 & vbCrLf & "己存在,是否覆寫?", 305, "警告") = vbCancel Then Exit Sub
  17.     End If
  18.     ' 重設起始位置
  19.     regType.i = 0
  20.     regType.Count = 0
  21.    
  22.     Open 檔名 For Output As FileNum     ' 重設資料檔
  23. Else
  24.     ' 取得最後中斷位置
  25.     Open 進度檔 For Binary As FileNum
  26.     Get #FileNum, , regType
  27.     Close #FileNum
  28.    
  29.     Open 檔名 For Append As FileNum     ' 追加資料
  30. End If

  31. Count = regType.Count

  32. For i = regType.i To 迴圈上限
  33.     If i Mod 100 = 0 Then
  34.         DoEvents    ' 取得中斷狀態
  35.         If 中斷旗號 Then
  36.             ' 記錄執行狀態與結果
  37.             regType.i = i
  38.             regType.Count = Count
  39.             
  40.             FileNum2 = FreeFile
  41.             Open 進度檔 For Binary As FileNum2
  42.             Put #FileNum2, , regType
  43.             Close #FileNum2
  44.             Exit For    ' 強制結束迴圈
  45.         End If
  46.     End If
  47.    
  48.     ' 處理耗時迴圈內的演算內容...
  49.     If i Mod 2 = 0 Then
  50.         Count = Count + i
  51.     Else
  52.         Count = Count - i
  53.     End If
  54.    
  55.     Print #FileNum, Count       ' 資料輸出
  56. Next
  57. Close #FileNum

  58. If i > 迴圈上限 Then
  59.     ' 執行完成, 回收進度設定
  60.     If Len(Dir(進度檔, vbArchive Or vbHidden Or vbNormal Or vbReadOnly)) = 0 Then Kill 進度檔
  61. End If
  62. End Sub
複製代碼
強化後的流程圖:



結論:

嚴格上來講本篇是只換湯不換藥的應用,可以追加的功能也許不少,全憑使用上的需求。我個人不太喜歡執行起來令表單有顯示反應呆滯狀況的程式,更不喜歡不能自由決定何時能暫停休息一下的程式,才會有這一大堆又臭又長怪模怪樣的方法來解決問題。

我個人能忍受表單毫無反應的時間是十秒至三十秒左右,超過了便會懷疑程式已經死當了,事實上也許未必是這種狀況,總不能讓使用者無法得知執行的進度吧?強迫使用者陪程式浪費時間玩猜謎是件很不道德的事,請盡量避免比較好。

嗯,還有多層迴圈如何中斷的題材可以研究。

本文同步刊載於:http://tw.myblog.yahoo.com/shege-1975/article?mid=2283
附件: 您所在的用戶組無法下載或查看附件
請問樓主
            上列的程式碼適用哪一種程式語言???看起來像是VB但是不太確定???!!!
             TKS!!!
就是用 VB 阿!祇是有些變數使用中文名稱而已
插個嘴,中文版的好處就是有支援中文全型變數名稱,請試試。

我個人討厭取名字,尤其是還要翻譯成英文的...請別打我。
本帖最後由 JOJO 於 2009-6-19 10:53 編輯

>我個人討厭取名字,尤其是還要翻譯成英文的...請別打我。
我也很討厭取名字,因為英文很破,
但我還是習慣用英文常常一個變數名稱 2,3 拾個字,
搞到最後有時還會超過極限不得不用縮寫.
取名字應該是很多程式設計師的惡夢吧!
所以我回答網友的問題時有時也會偷懶一下用中文.

對了看流程圖比較重要拉!寫程式寫多了會覺得有流程圖
比較好,我自己的系統很想畫流程圖,但一值不知道怎畫
畫流程圖推薦使用 Office 來做,看是要使用 Word 或是 PowerPoint 都行,我偏好前者。當然也有更好的軟體,只是我非常不喜歡灌一堆有的沒有的。

在這之前,我用小畫家來做,辛苦程度可想而知!!!箭頭都還要自己設計...

努力朝著圖文並茂邁進,作者可真不好當啊。
C語言學久後~~VB就不會想用中文當變數名稱~~看來是習慣

一般都是用部分單字組合方式

Switch_Merge
自主學習,超越一切
我的網站 - http://clonin.phpnet.us/
返回列表 回復 發帖