none
FileSystemWatcher 物件的失效問題 RRS feed

  • 一般討論

  • 請教各位先進,

    我現在遇到的問題是使用了FileSystemWatcher這個物件,並監控某個EXCEL,

    當使用者關閉EXCEL後,並將資料寫入資料庫,完成後寄出mail通知我和使用者完成更新。

    當程式剛開始啟動後都可以正常運作,但是總會再一段時間後失效,有時後一兩天就失效,有時後幾個禮拜後才失效,

    期間使用者都每天會去使用該EXCEL。

    請問監控失效的這個問題點是出在哪,假設程式沒問題,有可能是硬體的問題嗎?

    以下我貼出比較重點的程式,麻煩各位先進提供相關經驗,感謝。

     using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;

    using System.IO;
    using NPOI.HSSF.UserModel;
    using NPOI.HPSF;
    using NPOI.POIFS.FileSystem;
    using NPOI.SS.UserModel;
    using System.Threading;
    using System.Data.SqlClient;

    using System.Data.OracleClient;
    using System.Transactions;

    namespace MES_MDS_SS_0001
    {
        public partial class MES_MDS_SS_0001 : Form
        {
            //設定值
            private FileSystemWatcher _watch = new FileSystemWatcher();
            private string _watchDirectory = @"監控資料夾路徑";
            private string _watchFile = "xxx.xls"; //監控的檔案名稱

            public MES_MDS_SS_0001()
            {
                InitializeComponent();
                Watche_MDSCustchedule();
            }

            private void Form1_Load(object sender, EventArgs e)
            {
               
            }

            private void Watche_MDSCustchedule()
            {
                _watch.Path = _watchDirectory;
                //_watch.NotifyFilter = NotifyFilters.LastAccess; // | NotifyFilters.FileName | NotifyFilters.DirectoryName //設定所要監控的變更類型
                _watch.Filter = "*.xls";
                _watch.IncludeSubdirectories = false;
                _watch.EnableRaisingEvents = true;
                _watch.Changed += new FileSystemEventHandler(FileSystemWatcher_Changed);
            }

            private void FileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
            {
                if (e.Name == _watchFile)
                {
                    #region 判斷檔案是否尚未關閉,等待至檔案關閉後才往下執行
                    bool file_Lock = true;
                    do
                    {
                        Thread.Sleep(3000);
                        try
                        {
                            using (File.Open(e.FullPath, FileMode.Open, FileAccess.Read)) { file_Lock = false; }
                        }
                        catch
                        { }

                    } while (file_Lock);
                    #endregion

                    if (!file_Lock)
                    {
                        #region 解決執行兩次問題
                        _watch.EnableRaisingEvents = false;
                        Thread th = new Thread(new ThreadStart(
                            delegate()
                            {
                                Thread.Sleep(1000);
                                _watch.EnableRaisingEvents = true;
                            }
                         ));
                        th.Start();
                        #endregion

                        try
                        {
                            DataTable dt = GetExcelData(_watchDirectory + @"\" + _watchFile);

                            if (InsertData(dt))
                            {
                                SendMailMessage("Send@mail", "user@mail", "xxx.xls - 修改成功", "修改成功!");
                            }
                            else
                            {
                                SendMailMessage("Send@mail", "user@mail", "xxx.xls - 修改失敗", "修改失敗!");
                            }
                        }
                        catch(Exception ex)
                        {
                            SendMailMessage("Send@mail", "user@mail", "xxx.xls - 修改失敗", ex.Message.ToString());
                        }

                    }
                }
            }


            //使用NPOI提取EXCEL資料內容,並輸出成DataTable
            private DataTable GetExcelData(string path)
            {
                (略)
            }

            //將DataTable資料寫入資料庫
            private bool InsertData(DataTable dt)
            {
                (略)
            }

            //寄送mail
            private void SendMailMessage(string From, string SendTo, string Subject, string Body)
            {
                (略)
            }

        }
    }

    2011年11月18日 上午 06:31

所有回覆

  • 你有的 catch並沒做任何動作, 建議你應該在例外發生時寫入Log, 可以使用 [EventLog 類別] 來記錄例外.

    在現實生活中,你和誰在一起的確很重要,甚至能改變你的成長軌跡,決定你的人生成敗。 和什麼樣的人在一起,就會有什麼樣的人生。 和勤奮的人在一起,你不會懶惰; 和積極的人在一起,你不會消沈; 與智者同行,你會不同凡響; 與高人為伍,你能登上巔峰。
    2011年11月18日 上午 11:14
    版主
  • Excel 是獨占式存取

    複製一份到暫存目錄,用那個來做 GetExcelData

     


    論壇是網友平等互助 保證解答請至 微軟技術支援服務
    提問時,錯誤情境描述與錯誤訊息很重要,情境描述包含你做了什麼,預期的結果與實際發生的結果。一個最爛的問法範例:「我的電腦電腦怎麼不能開機?」誰知道你家是不是沒電還是你根本找不到電源鈕。
    2011年11月18日 下午 12:56
  • 的確小弟忽略這個地方,補上這個功能或許可以抓到問題點在哪..可能會需要花點時間去Try。

    不過我比較好奇的是,各位先進使用FileSystemWatcher 物件的時候都沒有遇到過監控失效的問題嗎? 換句話說會沒有觸發事件的情形。


    我記得MSDN好像有說到一個問題,如果監控的資料夾如果有大量的檔案進而觸發事件,會造成緩衝區溢位。

    http://msdn.microsoft.com/zh-tw/library/ded0dc5s(v=VS.80).aspx

     

    我現在自己猜測有以下幾個可能性(純粹猜測)

    1.我監控的資料夾並不會新增大量檔案,幾乎沒有新增檔案,只是使用者會經常性的去開啟我所監控的EXCEL檔案,

    難道程式監控的時間過久也會造成緩衝區溢位!?

     

    2.因為我們公司的電腦環境使用者都是使用終端機,也有虛擬機,是否有可能我監控的資料夾因為是在虛擬機上,

    如果虛擬機重新啟動,FileSystemWatcher 是否引發例外!?  如果是實體機,重新開機是否也會引發例外!?

     

    很多東西真的都一知半解,也不知道應該去哪找解答,Google繁體中文網頁已經找翻了,(抱歉英文很菜)

    請各位多幫幫忙,感恩。

    2011年11月20日 上午 06:08
  • 之前在寫這隻程式的時候本來是用這個方法,但是記得好像複製後再去開複製後的檔案,還是會獨佔,

    所以才用迴圈判斷使用者是否關閉的鳥方法 @@"

    因為是半年前寫的,之前發生問題的頻率不高,索性就將就著用,當掉就重開 (抱歉,因為還有很多程式要趕 ><)

     

    我會在找時間試試看是否當初寫法有問題,感謝您的回覆。

    不過如果能針對FileSystemWatcher 物件來分享使用經驗的話,小弟會更加感謝 ^^。

    拜託請多多提點。

    2011年11月20日 上午 06:17
  • 複製的被獨佔應該沒關係吧...

    你原始的沒被獨佔就可以讓使用者繼續改。


    論壇是網友平等互助 保證解答請至 微軟技術支援服務
    提問時,錯誤情境描述與錯誤訊息很重要,情境描述包含你做了什麼,預期的結果與實際發生的結果。一個最爛的問法範例:「我的電腦電腦怎麼不能開機?」誰知道你家是不是沒電還是你根本找不到電源鈕。
    2011年11月20日 下午 03:31
  • Hi,

    緩衝區溢位理論上是透過Error事件告知

    對使用上的影響應該是那次的詳細變動資料會遺失

    並不會讓整個FileSystemWatcher沒有效果

    另外想問的是你的NotifyFilters是否是設對的

    因為你那邊是註掉的

      //_watch.NotifyFilter = NotifyFilters.LastAccess; // | NotifyFilters.FileName | NotifyFilters.DirectoryName //設定所要監控的變更類型

    NotifyFilters.FileName印象中是對檔名修改才會觸發

    像是新建檔案~刪除檔案~或是改變檔案名稱這些會觸發

    但若是對已經建立的檔案去改變內容~是不會觸發的

    DirectoryName對你偵測單一檔案應該是不用設

    LastAccess會連開啟都算進去~這時候也不會有更動資料~是否是該設為LastAccess

    另外你提到檔案是放在虛擬機

    又會突然沒有監控的效果

    所以猜測可能是在設定_watch.EnableRaisingEvents = true;這邊就掛了

    看MSDN可以看到他會有幾個可能性會丟例外

    像是檔案不在~路徑不在等


    謙卑學習,持之以恆,才能不斷的Level Up http://www.dotblogs.com.tw/larrynung/

    2011年12月4日 下午 04:40
  • Hi,

    順帶一提,你程式有幾個點感覺不是很好~像是這邊不論檔案是否開啟~都勢必會等一次3000ms,還有不必要的變數存在

     

                    #region 判斷檔案是否尚未關閉,等待至檔案關閉後才往下執行
                    bool file_Lock = true;
                    do
                    {
                        Thread.Sleep(3000);
                        try
                        {
                            using (File.Open(e.FullPath, FileMode.Open, FileAccess.Read)) { file_Lock = false; }
                        }
                        catch
                        { }

                    } while (file_Lock);
                    #endregion

     

     

    也許可以改成類似下面的處理方式

     

                    #region 判斷檔案是否尚未關閉,等待至檔案關閉後才往下執行
                    do
                    {
                        
                        try
                        {
                            using (File.Open(e.FullPath, FileMode.Open, FileAccess.Read)) {break;}
                        }
                        catch
                        { }

     Thread.Sleep(3000);

                    } while (true);
                    #endregion

     

     

    另外像是下面這邊~什麼是解決執行兩次問題?還有就是為何要開Thread去開啟監控?!

                        #region 解決執行兩次問題
                        _watch.EnableRaisingEvents = false;
                        Thread th = new Thread(new ThreadStart(
                            delegate()
                            {
                                Thread.Sleep(1000);
                                _watch.EnableRaisingEvents = true;
                            }
                         ));
                        th.Start();
                        #endregion

     


    謙卑學習,持之以恆,才能不斷的Level Up http://www.dotblogs.com.tw/larrynung/
    2011年12月5日 上午 01:56
  • Hi: 請問你的問題解決了嗎 ?
    在現實生活中,你和誰在一起的確很重要,甚至能改變你的成長軌跡,決定你的人生成敗。 和什麼樣的人在一起,就會有什麼樣的人生。 和勤奮的人在一起,你不會懶惰; 和積極的人在一起,你不會消沈; 與智者同行,你會不同凡響; 與高人為伍,你能登上巔峰。
    2011年12月14日 下午 03:56
    版主
  • 這段時間有更新了一版,測試的結果還是會失效... 附上程式碼

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;

    using System.IO;
    using NPOI.HSSF.UserModel;
    using NPOI.HPSF;
    using NPOI.POIFS.FileSystem;
    using NPOI.SS.UserModel;
    using System.Threading;
    using System.Data.SqlClient;

    using System.Data.OracleClient;
    using System.Transactions;

    namespace MES_MDS_SS_0001
    {
        public partial class MES_MDS_SS_0001 : Form
        {
            //設定值
            private static FileSystemWatcher _watch = new FileSystemWatcher();
            private static string _watchDirectory = @"C:\";  //監控資料夾路徑
            private static string _watchFile = "xxx.xls";    //監控的檔案名稱
            private static string _copyfileDirectory = @"D:\";      //暫存複製EXCEL的資料夾路徑

            public MES_MDS_SS_0001()
            {
                InitializeComponent();
                Watche_MDSCustchedule();
            }

            private void Form1_Load(object sender, EventArgs e)
            {
               
            }

            private static void Watche_MDSCustchedule()
            {
                _watch.Path = _watchDirectory;
                //_watch.NotifyFilter = NotifyFilters.LastAccess; // | NotifyFilters.FileName | NotifyFilters.DirectoryName //設定所要監控的變更類型
                _watch.Filter = "*.xls";
                _watch.IncludeSubdirectories = false;
                _watch.EnableRaisingEvents = true;
                _watch.Changed += new FileSystemEventHandler(FileSystemWatcher_Changed);
                _watch.Error += new ErrorEventHandler(FileSystemWatcher_Error);
            }

     private static void FileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
            {
                if (e.Name == _watchFile)
                {
                    bool file_Lock = true;
                    string copyfileURL = _copyfileDirectory + @"\" + _watchFile;
                    FileInfo file = new FileInfo(_watchDirectory + @"\" + _watchFile);

                     //判斷檔案是否被開啟
                    try
                    {
                        using (File.Open(e.FullPath, FileMode.Open, FileAccess.Read)) { file_Lock = false; }
                    }
                    catch { }

                    if (!file_Lock)
                    {
                        file.CopyTo(copyfileURL, true); //複製EXCEL至暫存資料夾

                        try
                        {
                            DataTable dt = GetExcelData(copyfileURL);

                            if (InsertData(dt))
                            {
                                SendMailMessage("Send@mail", "user@mail", "xxx.xls - 修改成功", "修改成功!");
                            }
                            else
                            {
                                SendMailMessage("Send@mail", "user@mail", "xxx.xls - 修改失敗", "修改失敗!");
                            }
                        }
                        catch (Exception ex)
                        {
                            SendMailMessage("Send@mail", "user@mail", "xxx.xls - 修改失敗", ex.Message.ToString());
                        }
                        finally
                        {
                            //刪除暫存EXCEL
                            FileInfo copyfile = new FileInfo(copyfileURL);
                            copyfile.Delete();
                        }

                    }
                }
            }

            private static void FileSystemWatcher_Error(object source, ErrorEventArgs e)
            {
                Exception watchException = e.GetException();
                SendMailMessage("Send@mail", "user@mail", "xxx.xls", "監控物件發生錯誤: " + watchException.Message);

                _watch = new FileSystemWatcher();
                while (!_watch.EnableRaisingEvents)
                {
                    try
                    {
                        Watche_MDSCustchedule();
                    }
                    catch
                    {
                        System.Threading.Thread.Sleep(5000);
                    }
                }
            }

            //使用NPOI提取EXCEL資料內容,並輸出成DataTable
            private DataTable GetExcelData(string path)
            {
                (略)
            }

            //將DataTable資料寫入資料庫
            private bool InsertData(DataTable dt)
            {
                (略)
            }

            //寄送mail
            private void SendMailMessage(string From, string SendTo, string Subject, string Body)
            {
                (略)
            }


        }
    }

     

    有做更動的部分的方向

    1. 將原本等待使用者開啟的EXCEL關閉後再開啟的方式改成直接複製EXCEL至某個暫存資料夾

    2. 增加FileSystemWatcher_Error事件,當觸發溢位的例外時重新註冊監控的事件,程式碼參考來源:
        
    http://www.codeguru.com/csharp/.net/net_general/eventsanddelegates/article.php/c9113

    PS. 關於解決執行兩次問題的那段程式碼的來源來源:
         
    http://www.cnblogs.com/jzywh/archive/2008/07/23/filesystemwatcher.html

          主要就是因為當使用者開啟和關閉檔案都會觸發兩次,不論我怎麼去設定 _watch.NotifyFilter 這個屬性,以我這個case(監控使用者修改檔案),
          我不斷嘗試後的感想是,不要設定反而會比較單純。後來程式改成判斷使用者是否正在開啟檔案,如果是就不做任何事,反之則執行。

        搞了一陣子還是會失效...  XD

    2011年12月21日 上午 02:37
  • 有參考過 NotifyFilters列舉型別 說明嗎?

    NotifyFilters.LastWrite (檔案或資料夾上次有資料寫入的日期) 應該就是你想要的吧


    以下為簽名檔,如果你愛拉椅子坐那就是你的問題。
    先查MSDN文件庫
    再用GOOGLE搜尋
    才到論壇來發問

    這是論壇不是技術支援中心
    沒有人得無償解答你的問題

    在標題或文章註明很急
    不會增加網友回覆速度
    2011年12月21日 上午 03:04
  • 有的,NotifyFilters底下的型別都有試過,1.沒有設定、2.設定 LastWrite  3.設定 LastAccess 的情形都一樣,

    當使用者開啟檔案還是會觸發,

    使用者第一次儲存檔案也會觸發,第二次儲存不會觸發,關閉檔案也不會觸發

    但是如果開啟檔案後沒有做儲存的動作直接關閉檔案,關閉檔案時會觸發。

    都會執行兩次,只是最後一次執行的時機不一樣,很奇怪吧 @@ 以上監控檔案為EXCEL的情形。

    但是如果是監控TXT,NotifyFilters就可以正常運作了。

    感覺FileSystemWatcher 比較適合監控TXT。

     

    不過目前碰到的問題不在這裡,是程式執行一段時間後就會失效,觸發不到事件(以我的程式來說就是觸發不到FileSystemWatcher_Changed事件)

    2011年12月21日 上午 03:44
  • 我稍微改寫你的程式之後

    發現NotifyFilter設成CreationTime 是可以抓到 excel 按下儲存時 會trigger OnChange事件

    不過不確定是否會有失效的問題

    猜測可能跟Office在開啟檔案時有別於一般檔案的處理

     

        public partial class Form1 : Form
        {
            private  FileSystemWatcher _watch;
            private delegate void AddListItemDelegate(string data);
            private AddListItemDelegate AddListItem;
    
            public Form1()
            {
                InitializeComponent();
    
                AddListItem = new AddListItemDelegate(AddItem);
    
                _watch = new FileSystemWatcher();
                _watch.Path = @"D:\";
                _watch.NotifyFilter = NotifyFilters.CreationTime;
                _watch.Filter = "xxx.xls";
                _watch.IncludeSubdirectories = false;
                _watch.Changed += new FileSystemEventHandler(_watch_Changed);
                _watch.EnableRaisingEvents = true;
            }
    
            private void _watch_Changed(object sender, FileSystemEventArgs e)
            {
    
                if (listBox1.InvokeRequired)
                {
                    listBox1.Invoke(AddListItem,e.FullPath);
                }
                else
                {
                    listBox1.Items.Add(e.FullPath);
                }
            }
    
            private void AddItem(string data)
            {
                listBox1.Items.Add(data);
            }
        }
    
    

     

     

     


    以下為簽名檔,如果你愛拉椅子坐那就是你的問題。
    先查MSDN文件庫
    再用GOOGLE搜尋
    才到論壇來發問

     

    這是論壇不是技術支援中心
    沒有人得無償解答你的問題

     

    在標題或文章註明很急
    不會增加網友回覆速度
    • 已編輯 Alex_Lee 2011年12月21日 下午 07:05
    2011年12月21日 下午 07:04
  • 感謝 Alex_Lee 您的回覆,確實設定NotifyFilters.CreationTime 時,當使用者按下儲存後會觸發事件,

    因為我的情形是觸發後要將EXCEL內容取得需要的部分後,寫入數百~數千筆資料到資料庫,

    因為要取得EXCEL內容還要先複製一份到暫存資料表後再讀取(因為原來的EXCEL還被使用者獨佔),在刪除暫存EXCEL檔。

    擔心短時間內,使用者一次按了好幾次儲存,以上的動作重複做好幾次怕會有奇怪的問題,

    所以才以使用者關閉檔案的觸發才進行以上的動作(只做一次)

     

    不過往後如果有其他特殊需求,這個方式也許可以利用,還是要感謝您 ^^

    2011年12月22日 上午 02:31