首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> ASP.NET 基礎

ASP.NET Global.asax 實作應用程式級定時排程:System.Timers.Timer 於 Application_Start 啟動

     編輯:ASP.NET 基礎

適用情境

  • 定期清理暫存檔、過期 Session 資料
  • 定時同步外部 API 資料到本地快取
  • 每日生成報表、發送排程通知信
  • 任何「不需使用者觸發、背景自動執行」的工作

完整 Global.asax 範例

<%@ Application Language="C#" %>
<%@ Import Namespace="System.Timers" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.IO" %>

<script runat="server">
    private static Timer _timer;

    void Application_Start(object sender, EventArgs e)
    {
        // 應用程式啟動時啟動排程
        _timer = new Timer(60_000); // 60 秒 = 1 分鐘
        _timer.Elapsed += OnTimedEvent;
        _timer.AutoReset = true;    // 週期性觸發
        _timer.Enabled = true;      // 啟動
    }

    void Application_End(object sender, EventArgs e)
    {
        // 應用程式關閉時釋放資源
        if (_timer != null)
        {
            _timer.Stop();
            _timer.Dispose();
            _timer = null;
        }
    }

    void Application_Error(object sender, EventArgs e)
    {
        Exception ex = Server.GetLastError();
        // 記錄未處理例外至 Log/資料庫/EventLog
        // LogHelper.Error("Application_Error", ex);
    }

    void Session_Start(object sender, EventArgs e) { /* 可統計在線人數 */ }
    void Session_End(object sender, EventArgs e) { /* 僅 InProc 模式有效 */ }

    private void OnTimedEvent(object sender, ElapsedEventArgs e)
    {
        try
        {
            // ====== 這裡放入您的排程工作 ======
            // 範例:清理 7 天前的暫存檔
            string tempPath = Server.MapPath("~/TempFiles");
            if (Directory.Exists(tempPath))
            {
                foreach (var file in Directory.GetFiles(tempPath))
                {
                    FileInfo fi = new FileInfo(file);
                    if (fi.CreationTime < DateTime.Now.AddDays(-7))
                        fi.Delete();
                }
            }

            // 範例:呼叫 Web API 同步資料
            // using (var client = new HttpClient()) { await client.GetAsync("https://api.example.com/sync"); }
        }
        catch (Exception ex)
        {
            // 排程執行緒例外不會自動進入 Application_Error,必須自行捕捉記錄
            // LogHelper.Error("Timer_Job_Failed", ex);
        }
    }
</script>

關鍵設定細節

屬性/方法說明
Interval毫秒單位。範例 60,000 = 1 分鐘。正式專案常設 3600000 (1小時) 或 86400000 (1天)。
AutoReset = truetrue = 週期性;false = 單次觸發(需自行再次 Start)。
Elapsed 事件在背景執行緒(ThreadPool)觸發,**非** ASP.NET 請求執行緒,無 HttpContext、Session、Request 等物件。
靜態 _timer防止被 GC 回收;Application_End 必須 Dispose() 釋放非受控資源。

常見坑與解法

  1. 無法存取 HttpContext:排程在背景執行緒,HttpContext.Current 為 null。需透過 HostingEnvironment.MapPath("~/") 取得實體路徑,或建立獨立 DbContext 連線字串從 ConfigurationManager 讀取。
  2. IIS 應用程式集區回收/閒置關閉:預設 20 分鐘無請求回收、定期回收(預設 29 小時),會導致 Timer 停止。
    • 解法 A:IIS 管理員 → 應用程式集區 → 進階設定 → 「閒置逾時」設 0、「定期回收」停用(不建議生產環境)。
    • 解法 B(推薦):改用 Hangfire / Quartz.NET / Windows Service / Azure WebJobs / 搭配外部排程器(Windows 排程器 curl 執行 .ashx),脫離 IIS 生命週期。
  3. 多執行個體(Web Farm)重複執行:多台伺服器各自啟動 Timer 會重複跑 Job。需加分散式鎖(Redis SETNX、資料庫排程表狀態欄位)確保單一執行。
  4. 例外吞沒:Elapsed 內未 try-catch 會導致 Timer 靜默停止(.NET 4.0+ 預設行為)。務必在方法最外層包 try-catch 並記錄 Log。

進階:可設定間隔、啟停控制

// Web.config 
<add key="JobIntervalMinutes" value="30" />
<add key="JobEnabled" value="true" />

void Application_Start(object sender, EventArgs e)
{
    bool enabled = Convert.ToBoolean(ConfigurationManager.AppSettings["JobEnabled"]);
    if (!enabled) return;

    int minutes = Convert.ToInt32(ConfigurationManager.AppSettings["JobIntervalMinutes"]);
    _timer = new Timer(minutes * 60_000);
    _timer.Elapsed += OnTimedEvent;
    _timer.AutoReset = true;
    _timer.Enabled = true;
}

總結

Global.asax + Timer 是最輕量、零相依的 ASP.NET 排程方案,適合**低頻率、非關鍵、單機部署**的簡易背景工作。生產環境若需高可用性、排程視覺化、失敗重試、多節點協調,強烈建議改用 Hangfire(支援 SQL Server/Redis/PostgreSQL 儲存排程狀態、內建 Dashboard、Cron 運算式、延遲/週期/一次性任務),只需幾行程式碼即可平滑遷移。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved