本人使用服務調用Timer處理手機端和Pda端上傳至服務器的大量文件,有時會報拒絕訪問文件甚至服務崩潰的情況。因為Timer到期后,線程池線程會執行回調方法,如果回調方法執行時間太長,Timer會再次(在上次執行回調方法結束前)調用線程池新線程執行回調方法。此時,如果回調方法訪問共享資源,我們沒有加同步鎖,就會造成數據的不一致、拒絕訪問、多次處理相同資源甚至程序崩潰的錯誤。如本人的程序,思路是處理完文件A之后將其挪到其它備份查閱目錄,如果第一個線程處理A文件占用了很長時間,另一個線程再次處理A文件,此時第一個線程是不允許挪走A文件的,第二個正在讀取,有可能第三個,第四個甚至更多線程訪問A文件。這樣會造成A文件多次重復處理,向數據庫多次插入同樣的記錄,並且可能鎖死A文件。演示代碼如下:
nmespace TimerExample
{
class Program
{
static Timer _timer;
static void Main(string[] args)
{
//實例化timer,5秒之后執行,間隔20秒
_timer = new Timer(DealFile, null, 5 * 1000, 20 * 1000);
Console.ReadLine();
}
// 處理文件的方法會很占用時間
pivate static void DealFile(object state)
{
//通過比對隨機數查看執行情況
Random random = new Random();
int tempRandomData= random.Next();
Console.WriteLine("處理文件開始...," + tempRandomData);
//模擬處理文件的時間25秒
Thread.Sleep(25 * 1000);
Console.WriteLine("處理文件結束...,"+tempRandomData);
}
}
}
執行結果如下:
處理文件開始...,217262014
處理文件開始...,1304498158
處理文件結束...,217262014
處理文件開始...,918462592
處理文件結束...,1304498158
果我們使用Timer的意圖是為了定期掃描處理文件,不是為了並發提高速度(我們本來就不想用並發,否則是在Timer回調方法內使用多線程技術,而不會因為回調方法執行時間太長使得Timer自己產生多個線程)。我們可以在首次事例化Timer時,peroid傳入System.Threading.Timeout.Infinite,表示只執行一次timer,然后在回調方法內調用Change方法,timer.Change(interval,System.Threading.Timeout.Infinite),如此則是每次處理完回調方法之后才會再次在Interval時間之后執行回調方法,演示代碼如下
namespace TimerExample
{
class Program
{
static Timer _timer;
static void Main(string[] args)
{
//實例化timer,5秒之后執行,間隔20秒
_timer = new Timer(DealFile, null, 5 * 1000, Timeout.Infinite);//20 * 1000);
Console.ReadLine();
}
private static void DealFile(object state)
{
//通過比對隨機數查看執行情況
Random random = new Random();
int tempRandomData= random.Next();
Console.WriteLine("處理文件開始...," + tempRandomData);
//模擬處理文件的時間25秒
Thread.Sleep(25 * 1000);
Console.WriteLine("處理文件結束...,"+tempRandomData);
_timer.Change(20 * 1000, Timeout.Infinite);
}
}
}
執行結果:
處理文件開始...,40380869
處理文件結束...,40380869
處理文件開始...,1750026338
處理文件結束...,1750026338
由上執行可知,Timer的調用是在上次回調方法執行結束之后,才間隔interval(20秒)再次調用回調方法的
當然,此時執行回調方法的時間間隔是interval加上上一次回調方法執行所需時間。
