2008年3月27日 星期四

PB多線程自動排程設計(一)

多線程的設計主要是為了改進單線程自動排程的問題,因此設計成多線程的執行方式來解決,但由於PowerBuilder本身沒有辦法設計MultiThread,因此改用多程式方(MultiProcess)式來模擬此方法。

既然是MultiProcess,那麼需要2支程式,一支是排程程式(Scahdule runner),與工作執行程式(Work Processor)。

排程程式:屬於獨立運作的程式,其內容相當單純,只負責排程表(Scahdule Table)的掃描(Scan)與執行呼叫(Call),由於本身不需要處理工作,因此沒有被工作延滯的可能性,單純的處理排程表的掃描,也可以減少多餘的程式碼維護。

工作執行程式:被動式程式,裡面會撰寫所有的工作程式碼,此程式可以被多次執行,由於在windows下一支程式可以被多次執行,而且不會互相干擾(這是指程式本身,不包含都在讀取相同資料庫的資料表產生的LOCK),所以利用windows的MultiProcess特性來達成模擬multiThread效果。

呼叫意示圖:

多重呼叫亦試圖:

由上圖看,好像需要寫出很多的WorkProcessor程式,但其實不然,在PB的application的OPEN事件中,可以取得CommandLine參數,該Processor程式只需要將所有工作寫在裡面,然後利用CommadnLine來判斷需要執行哪個工作程式碼即可完成。

呼叫方式:
假設排程程式是app.exe,工作執行程式是proc.exe,當排程掃描到此時該執行A工作時,只需要執行
Run('proc.exe A')

當proc.exe執行時,在其OPEN事件的參數CommandLine即可得到一個字串"A",在使用Choose Case來判定執行的程式碼片段。

當然這樣的程式設計也有它的優缺點

優點:排程、工作獨立分離,不互相干擾時序,運作流暢度高。

缺點:程式維護複雜、錯誤追蹤相當困難。

範例程式下載

2008年3月26日 星期三

PB單線式自動排程程式設計(二)

繼上一篇PB單線式自動排程程式設計(一)

這篇來談談如何改進上一篇的STACK溢位問題吧。
其實這個方法並不難,但是要有足夠的觀念。

1.在原來的window上增加一個自訂事件ue_process
2.在window的共用變數(Instance variables)增加一個布林值
Boolean ib_busy = false

3.把原本在timer事件中的 程式碼全部移動到 ue_process事件中
4.在ue_process事件中的程式碼(不包含變數定義)開頭加入:
ib_busy = true

在程式碼最後加入:
ib_busy = false

5.在原本的timer事件中寫下下列程式碼:

If ib_busy = false Then
post event ue_process()
End If


原理是利用旗標變數ib_busy來阻擋事件被觸發,當ue_process被執行時 ib_busy被設定為true,假設此時工作A發生問題時,timer事件的再觸發執行時可以透過ib_busy避免ue_process被觸發,如此ue_process就不會有一直被stack起來的問題。

那為何程式要移至ue_process事件?這是因為程式若移至ue_process事件後該工作被執行時stack指標是處在ue_process上,當clock觸發timer事件時,由於timer事件並無被stack住,所以可以順利的執行,因此不會被擋住,該事件可以被順利處理不會stack起來。

當然此法是可以避免timer觸發事件無法消化的問題導致stack溢位而crash發生,但是卻也沒辦法保證其它工作可以正常執行,因為只要有其中一個工作不正常,整個程式依然處在停擺狀態。

比較好的方式就是利用多執行緒撰寫程式,可惜powerbuilder並非用來設計低階處理的程式工具,因此無法像C/C#班可以設計多執行緒;當然山不轉路轉,不能設計多執行緒,但可以設計多執行程式,所以另一種方法便是利用多程式執行方式達到此效果。

PB單線式自動排程設計(一)

簡介:
這裡所謂單線式排程程式,意指排程掃描與工作處理程式都寫在同一支程式裡面,由於PowerBuild 6程式本身無法像C#般寫成多執行緒的能力,因此在同時處理許多作業時,都得依照程式碼執行的STACK順序執行,故名為"單線式"

準備與設計:
1.首先必須要有一分工作排程清單,例如每日工作

這樣的資料表結構你可以設計成DataWindow,當然你如果要增加欄位或是功能,都是可以的,不過基本上的結構是如此。

結構中"時"、"分"的欄位是用來比對是否進行工作的時間點,當時間行程到達該時間點時就得進行該工作的執行。

"執行時間"則是代表此工作"被執行完成"的時間點,這個用來判斷該工作是否已經被執行,而不會被重複執行的要件。

"啟用"則是決定該工作是否需要被執行,有的人會以另一種觀點"暫停"來設計。

"工作名稱"顧名思義只是用來顯示的名稱,讓人一目瞭然知道此工作的用途。

"代號"則是代表該工作在程式碼運作時的實際作業。

2.設計視窗:
設計一個window(w_app),並將上述排程DataWindow(dw_1)加入裡面。











另外設計一個MultiLineEdit,用以觀察執行結果。

3.程式碼撰寫:
OPEN事件
由於排單位是以"分"為基礎比對單位,故時間的區隔就以"分"為準則,因此在w_app的Open事件中寫入Timer(60),表示以60秒觸發一次Timer事件。

TIMER事件

Long    ll_count , ll_row

ll_count = dw_1.RowCount()
For ll_row = 1 to ll_count
// 檢查該工作是否啟用,若不啟用則跳過
If dw_1.GetItemString(ll_row , 'active') <> '1' Then Continue

// 檢查當天是否已經被執行過,若執行過者不再被執行
If date(dw_1.GetItemDatetime(ll_row , 'work_time')) >= today() then Continue

// 檢查是否符合執行時間,需要被執行
If dw_1.GetItemNumber(ll_row , 'hour') = Hour(now()) and &
dw_1.GetItemNumber(ll_row , 'min') = Minute(now()) then


// 填入/覆寫執行時間
dw_1.SetItem(ll_row , 'work_time' , Datetime(today() , now()))

// 顯示執行訊息
mle_1.text += String(now() , 'hh:mm') + &
' 執行工作[' + dw_1.GetItemString(ll_row , 'name') + '] ~r~n'

// 依照代號執行需要做的事情
Choose Case dw_1.GetItemString(ll_row , 'code')
Case 'A'
// 執行工作A的程式碼......
Case 'B'
// 執行工作B的程式碼......
Case 'C'
// 執行工作C的程式碼......
Case 'D'
// 執行工作D的程式碼......
Case 'E'
// 執行工作E的程式碼......
End Choose
end if

Next


4.以上只要在各執行工作的Case中寫入程式碼就完成基本的單線自動排程程式,其他的部份再依照個人需求去增減功能即可,如排程增減/維護/存檔。

優點:
原理簡單、撰寫設計容易,維護也容易,適合於資料處理量少的工作。

缺點:
每次增加工作都必須修改程式碼。
遇到單一工作處理資料量過大需要較長的工作時間時(超過1分鐘者),排程容易崩潰,使排程失效,甚而使程式CRASH掉。

程式為何會CRASH?原因出在TIMER指令,假設第一次觸發TIMER事件後執行其中一個工作A,而A工作剛好發生問題(讀資料過久、無限回圈等等),當然程式主執行緒會一直處在等待A工作完成才能繼續,但是TIMER指令是由系統CLOCK觸發,因此在每分鐘觸發一次的情況下,TIMER事件的STACK會不斷累積直到溢位,當溢位發生時程式便CRASH了。

範例程式下載