排程作業(TsERP.SchedulerWorker)¶
TsERP 排程作業是一支獨立的 Windows Console 程式(
TsERP.SchedulerWorker.exe),由 Windows 工作排程器每 5 分鐘觸發一次,負責行事曆事件 Email 通知與AutoPilot 自動排程。本頁是給系統管理員 / DBA 看的部署與維運手冊。
功能簡介¶
舊版的 ERP 系統把這兩件工作放在 LogicBll.Timer.TimerBll 內的 DispatcherTimer,只有當有人開著 ERP 主程式時才會跑。實務上這帶來幾個痛點:
- 下班後沒人開 ERP,行事曆通知信不會寄出
- AutoPilot 自動排程必須仰賴某個使用者「掛著」ERP
- 多人同時開 ERP 時,每台都會跑同一份排程,造成重複通知
新版把排程邏輯抽成獨立 TsERP.SchedulerWorker.exe,部署到 server,由 Windows 工作排程器(schtasks)每 5 分鐘觸發一次。優點:
- 不需要使用者開 ERP,server 上自動跑
- 單一執行點,靠 Mutex 保證不重疊
- 可獨立升級,與 ERP 主程式解耦
- 失敗會留下 'F' 狀態 + log,方便管理員追查
與舊版 TimerBll 的差異
舊版 ERP 主程式仍保留 TimerBll 相關程式碼,但 MainWindowBtnTimerViewModel 已不再實際啟動該 timer(避免與本排程作業衝突)。請務必確認 server 上只有排程作業在跑,使用者端 ERP 不會重複觸發通知。
前置需求¶
| 項目 | 需求 |
|---|---|
| 作業系統 | Windows Server 2019 / 2022(或 Windows 10 / 11 Pro) |
| .NET Runtime | .NET 8 Desktop Runtime(x64) |
| SQL Server | 可從排程主機連線到 ERP 各公司資料庫 |
| Mailgun | 可從排程主機對外發出 HTTPS request 到 Mailgun API |
DatabaseList.xml |
路徑 c:/TEMPS/SqlLocation/DatabaseList.xml,包含目標 DB 連線資訊 |
| 服務帳號 | 一個專用本機 / 網域帳號,能讀 XML、寫 log、連 SQL、連網 |
資料庫變更(DBA 必做)¶
排程作業靠兩支單一職責 SP 撈出待處理事件——dbo.TWORK_SKD_GET_PENDING_NOTIFY(待 Email 通知)與 dbo.TWORK_SKD_GET_PENDING_AUTOEXEC(待 AutoPilot 自動執行)。需要 DBA 先在每個目標公司資料庫做下列改動。
歷史對照
這兩支 SP 取代了原 TWORK_SKD_NOTIFYEVENT(@QueryType=1 / =2 兩個分支),詳細決策見 ADR 0015。舊 SP 仍可保留 30 天再 drop。
1. 行事曆 base table 加欄位¶
行事曆事件的底層 table(即 Twork_skd_vw_eventitem view 對應的 base table)需要新增「執行開始時間」欄位,作為殭屍判定基準。
-- 範例 DDL,實際 table 名請依貴公司 schema 調整
ALTER TABLE [dbo].[行事曆事件 base table]
ADD [執行開始時間] datetime NULL;
GO
欄位型別必須是 datetime
CalendarBll.Update已通知 / Update已完成 多載會把 DateTime.Now 透過 yyyy/MM/dd HH:mm:ss 格式字串傳給 SP,欄位型別必須能容納此值。
2. 建立兩支撈待辦事件 SP¶
完整 DDL 在 SqlBI/TWORK_SKD_GET_PENDING_NOTIFY.sql 與 SqlBI/TWORK_SKD_GET_PENDING_AUTOEXEC.sql,DBA 直接執行即可。摘要:
TWORK_SKD_GET_PENDING_NOTIFY(@datetime) — 待 Email 通知¶
CREATE PROCEDURE [dbo].[TWORK_SKD_GET_PENDING_NOTIFY]
(@datetime datetime)
AS
BEGIN
SET NOCOUNT ON;
SELECT *
FROM dbo.twork_vw_calendarevent
WHERE 已審核 = 'Y'
AND 是否停用 <> 'Y'
AND 通知 = 'Y'
AND 通知帳號 <> ''
AND 已通知 <> 'Y'
AND @datetime >= 通知時間;
END
TWORK_SKD_GET_PENDING_AUTOEXEC(@datetime) — 待 AutoPilot 自動執行¶
CREATE PROCEDURE [dbo].[TWORK_SKD_GET_PENDING_AUTOEXEC]
(@datetime datetime)
AS
BEGIN
SET NOCOUNT ON;
SELECT TOP 20 *
FROM twork_vw_calendarevent
WHERE 已完成 NOT IN ('Y', 'F')
AND (已完成 <> 'R' OR 執行開始時間 < DATEADD(MINUTE, -10, @datetime))
AND 已審核 = 'Y'
AND 是否停用 <> 'Y'
AND 是否自動執行 = 'Y'
ORDER BY 預定時間 ASC;
END
關鍵點:
TOP 20對應Scheduler.BatchSize,每次最多 20 筆ORDER BY 預定時間 ASC依時間先進先出已完成 NOT IN ('Y','F'):已完成的 Y 與失敗的 F 都不再撈- 殭屍回收條件
(已完成 <> 'R' OR 執行開始時間 < DATEADD(MINUTE, -10, @datetime)):非執行中(不是 R),或雖是 R 但 10 分鐘前就開始 → 視為前一輪 crash 重撈 @datetime是殭屍判定基準時間,由 caller 傳當下時間,便於外部測試
3. 新增 SP dbo.TWORK_SKD_UPDATE_EVENT_STATUS¶
CalendarBll.Update已通知(pkid, status, time) 與 Update已完成(pkid, status, time) 多載會呼叫這支 SP 一次更新「狀態 + 執行開始時間」。請建立:
| 參數 | 對應 C# 端 | 說明 |
|---|---|---|
LP_P1 |
type | '1'=已通知、'2'=已完成 |
LP_P2 |
pkid | 事件主鍵 |
LP_P3 |
status | 'R'=執行中(Running) / 'Y'=完成 / 'F'=失敗 / ''=重設 |
LP_P4 |
timestamp | yyyy/MM/dd HH:mm:ss 字串;空字串代表不更新時間 |
當 LP_P1='2'(已完成分支)且 LP_P3='Y' 時,SP 會自動把 [完成時間] 設為 GETDATE();其他狀態(R/F/'')不動 完成時間。
CREATE PROCEDURE [dbo].[TWORK_SKD_UPDATE_EVENT_STATUS]
@LP_P1 varchar(1), -- 1 = 已通知, 2 = 已完成
@LP_P2 varchar(50), -- pkid
@LP_P3 varchar(1), -- 'R' / 'Y' / 'F' / ''
@LP_P4 varchar(20) -- yyyy/MM/dd HH:mm:ss 或 ''
AS
BEGIN
SET NOCOUNT ON;
DECLARE @StartTime datetime = NULL;
IF @LP_P4 <> ''
SET @StartTime = CONVERT(datetime, @LP_P4, 111);
IF @LP_P1 = '1'
UPDATE [行事曆事件 base table]
SET [已通知] = @LP_P3,
[執行開始時間] = CASE WHEN @LP_P4 = '' THEN [執行開始時間] ELSE @StartTime END
WHERE [pkid] = @LP_P2;
ELSE IF @LP_P1 = '2'
UPDATE [行事曆事件 base table]
SET [已完成] = @LP_P3,
[執行開始時間] = CASE WHEN @LP_P4 = '' THEN [執行開始時間] ELSE @StartTime END,
[完成時間] = CASE WHEN @LP_P3 = 'Y' THEN GETDATE() ELSE [完成時間] END,
[是否停用] = CASE WHEN @LP_P3 = 'Y' THEN 'Y' ELSE '' END
WHERE [pkid] = @LP_P2;
END
GO
SP 名稱
請依貴公司命名慣例調整 TWORK_SKD_UPDATE_EVENT_STATUS。Common 端是用 Procedure.TWORK_SKD_UPDATE_EVENT_STATUS.Name 取的常數,DBA 改完 SP 後若名稱有異,請同步通知開發端調整 Procedure 列舉。
部署步驟¶
1. 發佈 Console 程式¶
在開發機把 TsERP.SchedulerWorker 專案 publish 為 framework-dependent:
dotnet publish TsERP.SchedulerWorker/TsERP.SchedulerWorker.csproj `
-c Release `
-r win-x64 `
--self-contained false `
-o C:\Publish\Scheduler
或 self-contained(檔案大但不需在 server 裝 .NET):
dotnet publish TsERP.SchedulerWorker/TsERP.SchedulerWorker.csproj `
-c Release `
-r win-x64 `
--self-contained true `
-o C:\Publish\Scheduler
2. 複製到 server¶
把 C:\Publish\Scheduler\ 整個資料夾複製到 server,建議放:
C:\ERPDeploy\Scheduler\
├── TsERP.SchedulerWorker.exe
├── TsERP.SchedulerWorker.dll
├── appsettings.json
└── ...其他依賴 dll...
3. 建立 log 目錄¶
排程作業透過 SchedulerLog 把所有歷程(INFO 與 ERROR)寫到 C:\temps\scheduler\yyyy-MM-dd.txt,每天一個檔。首次執行時若資料夾不存在會自動建立,但建議事先建好並設好權限:
New-Item -Path "C:\temps\scheduler" -ItemType Directory -Force
icacls "C:\temps\scheduler" /grant "<服務帳號>:(OI)(CI)M"
指令說明¶
New-Item:
| 參數 | 意思 |
|---|---|
-Path "C:\temps\scheduler" |
建在這個路徑 |
-ItemType Directory |
建的是資料夾(不是檔案) |
-Force |
父資料夾 C:\temps\ 不存在時自動建出來;目標已存在時不報錯 |
icacls:Windows 內建的 ACL(檔案權限)編輯工具。
| 參數 | 意思 |
|---|---|
/grant |
授予權限(不是拒絕、不是取代既有權限) |
<服務帳號> |
佔位符,替換成實際帳號(見下方範例) |
(OI) |
Object Inherit — 套用到資料夾內所有檔案 |
(CI) |
Container Inherit — 套用到所有子資料夾 |
M |
Modify — 可讀 / 可寫 / 可刪檔,不能改權限或拿走所有權 |
服務帳號範例¶
依你工作排程器的「執行此工作時,請使用下列使用者帳戶」設定替換 <服務帳號>:
# 本機專屬服務帳號(手冊推薦做法)
icacls "C:\temps\scheduler" /grant "tsERPSched:(OI)(CI)M"
# 用 SYSTEM 帳號跑
icacls "C:\temps\scheduler" /grant "SYSTEM:(OI)(CI)M"
# 網域帳號(公司網域 DARBWARE,使用者 erpsvc)
icacls "C:\temps\scheduler" /grant "DARBWARE\erpsvc:(OI)(CI)M"
不知道目前用哪個帳號?開「工作排程器」 → 找到 TsERP Scheduler → 點選 → 看下方「一般」分頁的「執行此工作時,請使用下列使用者帳戶」欄位。
驗證¶
icacls "C:\temps\scheduler"
輸出應包含類似 tsERPSched:(OI)(CI)(M) 的行,代表已套用成功。
什麼情況可以省略 icacls?
若排程是用 Administrator 或具有同等權限的帳號跑(例如個人開發機自己當管理員),對 C:\temps\ 預設就有完整權限,第二條可跳過。正式環境若用低權限服務帳號則必設 — 不設會導致 SchedulerLog 寫檔失敗(但因為 try/catch 吞例外,會靜默失敗,磁碟上完全看不到 log 檔)。
4. 確認 DatabaseList.xml¶
排程作業共用 ERP 主程式的 SQL 連線設定,會讀取:
c:/TEMPS/SqlLocation/DatabaseList.xml
請確認此檔案於 server 存在,且含有 appsettings.json 中 Scheduler.Databases[*].Name 對應的 <DataBase> 條目。
5. 編輯 appsettings.json¶
打開 C:\ERPDeploy\Scheduler\appsettings.json,依環境調整(詳見配置檔說明)。
6. 手動跑一次驗證¶
以服務帳號身份開啟 PowerShell:
cd C:\ERPDeploy\Scheduler
.\TsERP.SchedulerWorker.exe
預期 console 輸出:
[SchedulerJob] === DB darbcylog1 on TWORKSQL ===
[SchedulerJob] Notify: no rows.
[SchedulerJob] AutoPilot: no rows.
[Scheduler] Done. exitCode=0
如果出現 Fatal error 或 exitCode 為 2,請先處理疑難排解再進入下一步。
服務帳號設定¶
建議不要用 Administrator 或一般使用者帳號跑排程作業,請建立專屬本機帳號:
1. 建立本機帳號¶
$pw = Read-Host -AsSecureString "Set password"
New-LocalUser -Name "tsERPSched" -Password $pw -PasswordNeverExpires -UserMayNotChangePassword
或在「電腦管理 → 本機使用者及群組 → 使用者」手動新增。
2. 賦予最小權限¶
| 權限 | 設定方式 |
|---|---|
以批次工作登入 (SeBatchLogonRight) |
本機安全性原則 → 使用者權限指派 → 「以批次工作登入」加入 tsERPSched |
讀取 c:\TEMPS\SqlLocation\DatabaseList.xml |
在檔案內容 → 安全性 → 加入 tsERPSched 給「讀取」 |
修改 C:\ERPDeploy\Scheduler\ |
同上,給「修改」 |
修改 C:\temps\scheduler\ |
SchedulerLog 寫入路徑(每日一檔) |
| SQL Server 連線 | 在 SQL Server 加入 Login,授予 ERP 各公司資料庫對應 SP 的 EXECUTE 權限 |
| 網路 | 確認防火牆允許對 api.mailgun.net HTTPS(443)出站連線 |
無互動登入即可
服務帳號不需要「允許本機登入」權限,建議於本機安全性原則「拒絕從本機登入」加入此帳號,降低被當作互動帳號濫用的風險。
Windows 工作排程器設定¶
1. 建立排程¶
開啟「工作排程器」 → 「建立工作」(不是簡單建立工作):
一般¶
- 名稱:
TsERP Scheduler - 使用者帳戶:
tsERPSched - 不論使用者是否登入皆執行:勾
- 以最高權限執行:依需求(通常不需勾)
- 設定:Windows Server 2019 / 2022 / 11
觸發程序¶
- 新增觸發程序,每日
- 開始:選擇生效日,時間 00:00:00
- 重複工作每:5 分鐘
- 持續時間:無限期
- 已啟用:勾
動作¶
- 動作:啟動程式
- 程式或指令碼:
C:\ERPDeploy\Scheduler\TsERP.SchedulerWorker.exe - 開始於(選擇性):
C:\ERPDeploy\Scheduler\
條件¶
- 只有在電腦使用 AC 電源時才啟動工作:取消勾選(server 不需要)
- 只有在下列網路連線可用時才啟動:依環境
設定¶
| 選項 | 建議值 |
|---|---|
| 允許隨選執行此工作 | 勾 |
| 如果排定的開始時間錯過則盡快執行此工作 | 勾 |
| 如果工作失敗,每隔重新啟動 | 不勾(讓下一個 5 分鐘自然觸發即可) |
| 停止超過下列時間的工作 | 勾,4 分鐘(hard timeout,防止某次 hang 卡住下次觸發) |
| 如果執行中的工作未在要求時結束,將強制停止 | 勾 |
| 如果工作未排定再次執行則在以下時間之後刪除 | 不勾 |
| 如果工作已在執行則使用下列規則 | 不要啟動新的執行個體 |
務必設「不要啟動新的執行個體」
雖然程式內部已有 Mutex 保護,但工作排程器層級也要設成「不重疊」,避免同時噴出多個 process 競爭 log 檔案 handle。
2. 用 schtasks CLI 建立(替代方式)¶
schtasks /Create ^
/TN "TsERP Scheduler" ^
/TR "C:\ERPDeploy\Scheduler\TsERP.SchedulerWorker.exe" ^
/SC MINUTE /MO 5 ^
/RU "tsERPSched" /RP "***" ^
/RL LIMITED ^
/F
CLI 設定完仍建議在 GUI 確認「設定」分頁的「不要啟動新的執行個體」與「4 分鐘 timeout」是否正確。
配置檔說明¶
appsettings.json 結構:
{
"MailGun": {
"ApiKey": "<your-mailgun-api-key>",
"Domain": "mg.darbware.com",
"Sender": "@\"TWork ERP Mail Service <tworkservice@mg.darbware.com>\""
},
"Scheduler": {
"Databases": [
{ "Name": "darbzdcsa1", "ConnectionString": "" }
],
"AlertRecipients": ["admin@example.com"],
"ZombieTimeoutMinutes": 10,
"BatchSize": 20,
"DefaultSrvdbid": 1
}
}
MailGun 區段¶
| 欄位 | 說明 |
|---|---|
| ApiKey | Mailgun API Key(私鑰,請勿外流) |
| Domain | Mailgun 域名 |
| Sender | 寄件人字串,含格式化引號 |
Scheduler 區段¶
| 欄位 | 必填 | 預設 | 說明 |
|---|---|---|---|
| Databases | 是 | [] |
要處理的公司資料庫清單,留空陣列代表跑 DatabaseList.xml 內全部 DB。Name 對應 XML 內 <DataBase> 名稱,須完全一致(不分大小寫) |
| Databases[*].ConnectionString | 否 | "" |
預留欄位;目前實際連線仍以 DatabaseList.xml 為準,這裡填了不會生效 |
| AlertRecipients | 否 | [] |
致命錯誤時的告警收件人(保留欄位,目前由 SchedulerLog 處理;未來可擴充用) |
| ZombieTimeoutMinutes | 否 | 10 |
殭屍回收門檻(分鐘)。注意:實際判定寫死在 SP 內的 DATEADD(MINUTE, -10, ...),此欄位目前是給 SP 端參考的契約值,調動時請同步改 SP |
| BatchSize | 否 | 20 |
每次最多處理筆數。注意:實際 TOP 20 寫死在 SP 內,調動時請同步改 SP |
| DefaultSrvdbid | 否 | 1 |
SchedulerUser.Srvdbid 預設值,影響 LoggingId / log 路由 |
ZombieTimeoutMinutes / BatchSize 是契約值
這兩個欄位目前不會主動傳到 SQL 端,SP 內的 TOP 20 與 DATEADD(MINUTE, -10) 是寫死的。把它放在 config 是為了讓管理員一眼看到「殭屍 = 10 分鐘」「批次 = 20 筆」這兩個約定,真正要改值請改 SP。
多公司支援¶
跑單一公司¶
"Databases": [
{ "Name": "darbcylog1" }
]
跑多公司(依序,同一 process 內逐個切 connection)¶
"Databases": [
{ "Name": "darbcylog1" },
{ "Name": "darbzdcsa1" },
{ "Name": "darbtest" }
]
排程作業會依序處理每個 DB(呼叫 IDatabaseConnection.ChangeParameter),每個 DB 各自跑一輪 Notify + AutoPilot。某個 DB 失敗不影響其他 DB。
跑全部 DB¶
"Databases": []
留空陣列時,排程作業會把 DatabaseList.xml 內的所有 DB 都掃過一遍。
多公司 + 4 分鐘 timeout 的取捨
如果有 5 個以上公司、每家都有大量待處理事件,5 分鐘觸發 + 4 分鐘 hard timeout 可能不夠。建議:
- 拆成多支排程作業(複製目錄、各自
appsettings.json跑不同 DB) - 或調高
BatchSize並把 timeout 拉到 8 分鐘,但觸發改成每 10 分鐘
失敗處理 / 重設 'F' 狀態¶
'F' 狀態怎麼來¶
當 MailGun 寄信失敗、AutoPilot 動作 throw exception、或回傳 IsSuccess = false 時,排程作業會把該事件的 已通知 或 已完成 標為 'F',並在 log 留下原因。
'F' 狀態不會被自動重撈(SP 過濾 NOT IN ('Y', 'F')),需要管理員手動處理。
重設讓事件再跑一次¶
在 SQL Management Studio 執行:
-- 把單一事件重新放回待處理
UPDATE [行事曆事件 base table]
SET [已通知] = '', -- 或 'N',依貴司 default
[執行開始時間] = NULL
WHERE [pkid] = '<目標 pkid>';
-- AutoPilot 同理
UPDATE [行事曆事件 base table]
SET [已完成] = '',
[執行開始時間] = NULL
WHERE [pkid] = '<目標 pkid>';
批次重設今天所有失敗事件¶
UPDATE [行事曆事件 base table]
SET [已通知] = '',
[執行開始時間] = NULL
WHERE [已通知] = 'F'
AND CAST([預定時間] AS date) = CAST(GETDATE() AS date);
先確認失敗原因
重設前請先看 log(C:\temps\scheduler\yyyy-MM-dd.txt)確認失敗原因。如果是 Mailgun 帳號爆量、收件人 email 格式錯誤,重設只會再失敗一次。先修根因再重設。
殭屍 'X' 狀態¶
什麼是殭屍¶
排程作業在處理一筆事件時,會先把狀態標為 'X'(執行中)並寫入 執行開始時間 = DateTime.Now。正常情況下幾秒內就會更新成 'Y'(成功)或 'F'(失敗)。
但如果 process 在這當中被強制終止(OOM、Windows 關機、被 timeout 殺掉、藍屏),事件會卡在 'X' 永遠不會被更新 → 殭屍。
自動回收¶
SP 的查詢條件包含:
[已完成] <> 'X' OR [執行開始時間] < DATEADD(MINUTE, -10, GETDATE())
意思是:'X' 狀態超過 10 分鐘的事件會被當成殭屍重新撈出。下次觸發排程作業時,這些殭屍會被重跑。
手動處理¶
若要立刻把殭屍復活(不等 10 分鐘):
UPDATE [行事曆事件 base table]
SET [已通知] = '',
[執行開始時間] = NULL
WHERE [已通知] = 'X';
為什麼是 10 分鐘¶
排程是 5 分鐘觸發 + 4 分鐘 timeout,正常一次跑滿不會超過 4 分鐘。10 分鐘給予 1 個完整週期的緩衝,避免「上一輪還沒跑完」就被當殭屍。
疑難排解 (FAQ)¶
信沒寄出去怎麼辦?
依序檢查:
- 手動跑一次(
TsERP.SchedulerWorker.exe),看 console 輸出。如果出現Notify pkid=... -> F (...),看括號內錯誤訊息 - 看 log:
C:\temps\scheduler\yyyy-MM-dd.txt,找最近的[ERROR] [SchedulerJob] Notify pkid=... -> F (...)行 - 確認 Mailgun:API Key 是否過期、Domain 是否被停用、額度是否爆
- 確認收件人:行事曆事件的
通知帳號欄位是否有效 email - 確認 SP:手動
EXEC dbo.TWORK_SKD_GET_PENDING_NOTIFY @datetime = GETDATE()看有沒有撈到 row。若沒有,問題在 SP 的過濾條件
AutoPilot 沒跑怎麼辦?
- 確認該事件的
通知方式不是 Email(AutoPilot 走的是LP_P1='2'流程,跟通知信不同) - 看 log 是否有
AutoPilot pkid=... -> F - 確認
IAutoPilotHelper.ExecuteMethod對該事件回傳的Message - AutoPilot 動作本身可能依賴主程式的某些 plugin / DLL,請確認
TsERP.SchedulerWorker部署目錄含有對應 DLL
log 看哪裡?
SchedulerLog(主要 log):C:\temps\scheduler\yyyy-MM-dd.txt,每天一個檔,含完整 INFO + ERROR 歷程- stdout / stderr:手動執行
.exe時可在 console 看到;工作排程器啟動時 stdout 會被丟棄(已不需要靠 stdout 看歷程,SchedulerLog已涵蓋) - Mailgun 端:Mailgun Logs
- 行格式:
yyyy-MM-dd HH:mm:ss [LEVEL] 訊息,例:2026-05-05 02:30:00 [INFO] [SchedulerJob] === DB darbnogilog1 on localhost\t2022bin ===
exit code 1 跟 2 有什麼差別?
| exit code | 含義 | 處理方式 |
|---|---|---|
| 0 | 全部成功(含「沒有資料要處理」) | 無需處理 |
| 1 | 部分失敗:某個 DB 的 NotifyAsync 或 AutoPilotAsync 拋例外,但其他 DB 仍跑完 | 看 log 找哪個 DB 失敗,多半是 SP 沒改、權限不足、SQL 連線問題 |
| 2 | 致命錯誤:根本沒進入主流程(DI 註冊失敗、appsettings 解析失敗、Mutex 競爭以外的問題) | 看 stderr 第一行 Fatal error: ...,通常是設定檔錯誤或缺 DLL |
排程作業會跟正在開 ERP 的使用者衝突嗎?
不會。新版 ERP 主程式的 MainWindowBtnTimerViewModel 已移除 TimerBll 欄位,使用者端不會再啟動本機 timer。所有通知/AutoPilot 都由 server 上的排程作業統一處理,避免重複。
舊版 client 升級前的過渡期
若 server 已啟用排程作業、但部分使用者還在用舊版 ERP client(仍含 TimerBll 啟動邏輯),會出現重複通知。請務必同步升級所有 client,或暫時停用 server 排程作業。
前一次還沒跑完,下一次又被觸發了,怎麼辦?
程式內以 Global\TsERP_Scheduler Mutex 保護單一執行;後到的 process 會印 Another scheduler instance is running. Exiting. 後 exit code 0 結束。工作排程器層級也設了「不要啟動新的執行個體」雙保險。
若想驗證,可手動連續執行兩次 TsERP.SchedulerWorker.exe,第二次應立即結束。
排程作業要怎麼停?
工作排程器 → 找到 TsERP Scheduler → 停用。已在執行中的 process 會跑完當下這輪,不會被中斷(除非超過 hard timeout)。
若要立即終止 process:在工作管理員 kill TsERP.SchedulerWorker.exe。下次觸發前殭屍 'X' 狀態會自動回收。
可以調整觸發間隔嗎?
可以。修改工作排程器觸發程序的「重複工作每 5 分鐘」為其他值即可。但請注意:
- 間隔太短(< 2 分鐘)會頻繁敲 SQL,影響效能
- 間隔太長(> 30 分鐘)通知信延遲體感很差
- 改間隔後請同步調整 hard timeout(建議 = 觸發間隔 - 1 分鐘)與 SP 的殭屍判定門檻(建議 = 觸發間隔 × 2)
截圖補充清單¶
以下截圖建議補上,可放於 docs/admin/images/:
- [需補:scheduler-task-general.png] — 工作排程器「一般」分頁完成設定畫面
- [需補:scheduler-task-trigger.png] — 工作排程器「觸發程序」分頁,5 分鐘重複設定畫面
- [需補:scheduler-task-action.png] — 工作排程器「動作」分頁,指向 exe 的設定畫面
- [需補:scheduler-task-settings.png] — 工作排程器「設定」分頁,4 分鐘 timeout 與「不要啟動新的執行個體」畫面
- [需補:scheduler-console-output.png] — 手動執行 exe 的 console 成功輸出畫面
- [需補:scheduler-log-folder.png] —
C:\temps\scheduler\內的 log 範例