先固定容量與索引契約
本例有Capacity=1000,合法索引是0到999,Total=1000代表要檢查全部一千筆。工作開始時NextIndex=0,每掃描最多處理25筆,因此需要40批完成。先把容量、總數與索引寫成契約,避免把陣列長度、有效資料數和下一筆位置混為一談。
| 名稱 | 本例值 | 含義 |
|---|---|---|
| Capacity | 1000 | 陣列可容納筆數 |
| Total | 1000 | 本次有效資料筆數 |
| NextIndex | 0 | 下一批起點 |
| BatchSize | 25 | 每批上限 |
| 批次數 | 40 | 1000÷25 |
每次執行前先檢查0≤NextIndex≤Total≤Capacity。若Total=1001而Capacity仍是1000,結果必須是CAPACITY_ERROR,不能把第1000筆寫入陣列,也不能用『最後一批只有26筆』掩蓋超界。若需求真的要處理1001筆,先把容量明確改成至少1001,再重新建立來源快照與測試。
這個工作只做離線資料加總,沒有寫入致動器。先把輸入凍結成固定版本,初始化WorkSum為0、WorkCount為0、Busy為TRUE、ResultValid為FALSE。PublishedSum保留上一次完整工作的數字供顯示,但目前有效旗標為假。不要在開始新工作時把舊結果和新的進度混在同一個顯示欄,否則使用者會把未完成的部分值當成正式結果。
計算每批數量避免無號下溢
合法範圍通過後,Remaining=Total-NextIndex,n取BatchSize與Remaining的較小值。當NextIndex=975、Total=1000時,Remaining=25,n=25;處理完才把NextIndex推到1000。當NextIndex=1000時,Remaining=0,工作已完成,絕不可執行FOR 0 TO n-1。若n是無號型別,n-1在n=0時可能下溢成很大的數。
| NextIndex | Total | Remaining | n | 動作 |
|---|---|---|---|---|
| 0 | 1000 | 1000 | 25 | 處理0..24 |
| 25 | 1000 | 975 | 25 | 處理25..49 |
| 950 | 1000 | 50 | 25 | 處理950..974 |
| 975 | 1000 | 25 | 25 | 處理975..999 |
| 1000 | 1000 | 0 | 0 | 新空工作完成 已完成工作不再重發Done |
安全的教學偽碼(不可直接編譯)是:先驗證範圍;若NextIndex=Total則進入完成分支;否則n=MIN(25,Total-NextIndex),只有n>0才進入迴圈。索引使用NextIndex+k,且每次存取前仍可檢查小於Capacity。
控制迴圈的變數與總和應分別選型。索引需要表示0到1000這個完成哨兵值,總和需要表示499500;十六位元有號整數無法容納總和,即使每一筆Raw最大只有999。計算範圍應包含中間批和與累計值。本例整數相加可用足夠寬的型別精確驗算,實作前仍要查目標PLC的整數寬度與溢位行為。
用Raw[i]=i檢算每批結果
為了讓結果可手算,設定Raw[i]=i,i從0到999,檢查工作計算所有資料的總和。完整預期值是0+1+…+999=499500。第一批索引0到24的和是(0+24)×25÷2=300;第二批25到49的和是(25+49)×25÷2=925。這兩筆具體數值可抓出起點偏移或少算一筆。
| 批次 | 索引範圍 | 筆數 | 批和 | 累計 |
|---|---|---|---|---|
| 1 | 0..24 | 25 | 300 | 300 |
| 2 | 25..49 | 25 | 925 | 1225 |
| 39 | 950..974 | 25 | 24050 | 474825 |
| 40 | 975..999 | 25 | 24675 | 499500 |
第39批的批和為(950+974)×25÷2=24050;第40批為(975+999)×25÷2=24675。前38批累計加上這兩批後,最終必須是499500。結果先寫到WorkSum與WorkCount,每個小批成功後只更新工作進度;直到全部1000筆完成,才把WorkSum一次發布為PublishedSum並令ResultValid為TRUE。
第39批提交後,WorkCount為975,WorkSum為0到974的總和474825;再加最後25筆的24675,才得到499500。若某批驗證失敗,先前已完成的WorkSum保留供診斷,但ResultValid仍為假。重新開始採新工作,不從錯誤索引續跑;本例也不允許Busy期間的新請求覆蓋目前來源版本。
來源快照也要算進時間預算
分批檢查只能限制每次迴圈的工作量,不能自動解決來源不一致或快照搬移成本。若在工作開始時複製1000筆,複製本身可能超過掃描預算,必須把搬移時間、記憶體占用與其他任務一併估算。若資料來源可固定為不可變版本,保存SourceVersion並讓每批讀取同一版本;若來源會變動,版本改變時不能把舊NextIndex套到新排列。
| 策略 | 一致性 | 時間成本 | 本例處置 |
|---|---|---|---|
| 完整快照 | 高 | 一次搬1000筆 | 納入預算再決定 |
| 不可變來源 | 高 | 不搬整表 | 保存Version |
| 每批直接讀取 | 可能混合 | 較低 | 來源變更即錯誤 |
| 雙份來源切換 | 依發布協定 | 需兩份容量 | 待平台驗證 |
合成單筆20微秒時,25筆約500微秒,1000筆理論約20毫秒;這只是估算,不是硬體測量。實際還要加入索引、判斷、來源讀取、結果寫入、任務切換和通訊成本。應用目標CPU量測最長批次時間,再決定25是否適合。
分批會增加牆鐘完成時間,不能只看計算量變少。若理想任務每10毫秒啟動一次,第一批在0毫秒開始,第40批約390毫秒開始,之後還要加最後一批的實際執行時間。這不是20毫秒內完成一千筆的同一種服務。需求若同時要求短掃描與很短總完成時間,就要重新評估硬體、算法或可接受的資料筆數。
Cancel與整批結果發布
本例固定規則:Cancel只在每批開始取樣。掃描進入批次時若Cancel=1,工作轉CANCELLED,不執行該批;若批次執行中才收到Cancel,當批仍完成,下一批開始前才取樣;若已是最後一批,則完成先於尚未取樣的取消,此次結果仍為DONE。這避免在單筆中途停止造成WorkSum與WorkCount半批發布。CancelRequested、CancelSampled與Cancelled結果分開記錄。
| 時刻 | NextIndex | Cancel | 結果 |
|---|---|---|---|
| 批次3開始 | 50 | 0 | 執行50..74 |
| 批次3中途 | 仍50 | 1 | 本批完成,不立即停 |
| 批次4開始 | 75 | 1 | 取樣後CANCELLED |
| 取消後 | 75 | 1 | Done=false,保留已完成50..74 |
| 重新開始 | 依命令 | 0 | 新工作重新快照 |
每批結果先放暫存區,包含批次起點、筆數、批和、失敗筆數與SourceVersion。n筆全部完成才提交;任何一筆檢查失敗,依本例策略回ERROR且不發布該批部分結果。Done只有全部資料完成、最後批已提交,且本次批首沒有接受Cancel、過程沒有ERROR時才為真;未取樣的取消不追溯推翻已完成結果。
CancelRequested應保持到工作者確認或終態,不能只送一個比任務週期短的脈衝。以第三批中途取消為例,第三批全部完成後NextIndex為75,WorkSum為0到74的和2775;第四批起始接受取消,因此PublishedSum不更新、ResultValid為假。這組數字同時檢查取消延遲與沒有發布半成品兩個要求。
邊界排查 FAQ與來源
完成後應看到40次小批提交、WorkCount為1000、WorkSum與PublishedSum皆為499500,ResultValid為真。Done只在進入完成終態的那次呼叫為真,後續呼叫清回假且不再存取陣列。Total為0的新工作則回傳有效總和0並完成一次;若只是已完成工作再次被呼叫,不能把它當成另一個空工作重發完成事件。
排查先看Capacity、Total、NextIndex是否通過不等式,再看n=0是否避開迴圈,接著核對每批起訖索引與WorkCount。若總和不是499500,先比對第一批300與第二批925,再查最後批是否完整包含999。若1001筆被接受,立即查容量驗證;這是錯誤而非正常延伸。
問:每批25筆一定不會超時嗎?不一定,20微秒只是合成估算,仍需量測最長批次。問:Cancel一到就停可以嗎?本例固定在批次起始取樣,批內取消下批才接受。問:PartialSum能先顯示嗎?可以顯示工作進度,但不能把它標成完成結果。問:1001筆如何處理?先把Capacity改到至少1001並重新驗證,Capacity=1000時必須回CAPACITY_ERROR。
本文偽碼不可直接編譯;需依目標PLC的陣列界限、無號整數、任務週期、看門狗與資料快照功能改寫。案例為離線手算。
參考:CODESYS array declaration and bounds
參考:CODESYS 任務設定