寫網站時有時需要產生暫存檔,過去我慣用的做法是透過System.IO.Path.GetTempPath(),將暫存檔寫入Windows系統暫存資料夾,如意算盤是暫存資料夾本來就是放暫存檔的地方,而Windows有機制可在磁碟空間不足時刪除暫存檔釋放空間,如果檔案沒有機密或敏感性,放在暫存資料夾基本上可以Fire and Forget,用過就放著不管,很省事。

後來幾次部署經驗告訴我,「ASP.NET程式不一定有寫入暫存資料夾的權限」(登楞!)。針對Temp資料夾開放ASP.NET寫入權限是一種解法,但每個ASP.NET應用程式的執行身分不同,搞下來Temp資料夾需維護一堆權限有點複雜。相形之下,App_Data隸屬各ASP.NET應用程式,可部署時一併設定權限統一管理,相對簡便。(註:適合放暫存檔是因為App_Data具有隱身特性,請不要將暫存檔放在一般網站目錄下,以免被使用者不當存取)

但暫存檔放App_Data就不像系統暫存資料夾可以靠Windows機制回收空間,得自行清理過期暫存檔。最直覺做法是寫個排程每天定時刪檔,但最近我想出另一個更省事的做法-取得暫存目錄時自動清理過期檔案。程式範例如下,分享兼請大家Code Review:P

    //記憶上次清理執行日期
    static string lastAttTempCleanDate = null;
    //非同步鎖定
    static object cleanLock = new Object();
 
    static string GetTempPath()
    {
        //使用App_Data暫存檔案,避免ASPX沒有寫入系統TEMP權限
        string tempPath = Path.Combine(
            HttpContext.Current.Server.MapPath("~/App_Data"), "UploadAtts");
        //若目錄不存在,建立之
        Directory.CreateDirectory(tempPath);
        //每次啟動網站或每天清理一次過期Cache
        string todayDate = DateTime.Today.ToString("yyyyMMdd");
        //防止多執行緒同時觸發
        lock (cleanLock)
        {
            //比對上次執行日期,原則上每天只執行一次
            if (todayDate != lastAttTempCleanDate)
            {
                //先儲存執行日期記錄,避免重複執行
                lastAttTempCleanDate = todayDate;
                //另開Thread執行刪除,以免卡住目前的暫存作業
                Task.Factory.StartNew(() =>
                {
                    //決定逾期判斷基準
                    DateTime expireTime = DateTime.Today;
                    foreach (var file in Directory.GetFiles(tempPath))
                    {
                        //若檔案建立時間早於今天,刪除之
                        if (File.GetCreationTime(file).CompareTo(expireTime) == -1)
                        {
                            File.Delete(file);
                        }
                    }
                });
            }
        }
        return tempPath;
    }


Comments

# by 毛豆

但暫存檔放App_Data就像系統暫存資料夾可以靠Windows機制回收空間 (X) 但暫存檔放App_Data就不像系統暫存資料夾可以靠Windows機制回收空間 (O)

# by Jeffrey

謝謝毛豆,巧妙地由指間流洩出各式錯字漏詞倒序亂排一定是一種天賦(遠目)可是我一點都不想要啦~ orz

# by Slash

建議考慮將lock移到task內會比較好,因為當task運行比較久時(一大堆檔案待砍),本身這個thread早已經release掉了(尤其好發於async Task),那麼下一個程式碼中再調用GetTempPath()方法時依然不會被lock到,lock意義就顯得很淡了。

# by Jeffrey

to Slash, lock的目的在保護單一時間只能有一個Thread執行todayDate != lastAttTempCleanDate比對,避免同時兩條Thread判定todayDate != lastAttTempCleanDate而觸發兩個Task起來砍檔。 砍檔Task執行需要一段時間,但由於執行前已lastAttTempCleanDate = todayDate,不會再觸發第二個砍檔Task。若lock放在Task中,有可能同時觸發n個砍檔Task,第1個Task執行,第2-n個Task等待第1個Task執行完,又輪番重掃一次檔案(但逾時檔案已被第一個Task砍光)空耗效能。 希望這樣有解釋將lock放在外圍的用意。

# by Slash

嗯,你說的沒錯,如果將Lock移到Task內的確反而會引發這方面的問題,我了解你的設計方式了。 另外建議,你用static來存資料很By server,如果顧慮到AP重開機、或者在LB的共用檔案目錄的環境下,我會選擇做一個自己可視別的小TXT檔,丟在檔案目錄內來做為File.Exists參考。 畢竟,他都是垃圾桶了,剩一個接近0K的小檔案無妨,哈哈!

Post a comment