2012-06-25 44 views
3

我有一個應用程序監視目錄的變化。當一個文件(一個日誌文件)被創建時,它會分析它的內容,將結果寫入一個表單中(它已經存在並且已經被初始化,儘管可能已經隱藏了),並且最終將這個表單顯示給用戶。如果在之前沒有顯示過,請填寫「無響應」

當應用程序啓動時,只有一個圖標顯示在任務欄中。主要方法只是在任務欄中創建圖標,並初始化觀察/分析並用結果控制表單的類。

public static void Main(string[] args) { 
    NotificationIcon notificationIcon = new NotificationIcon(); 
    notificationIcon.notifyIcon.Visible = true; 
    if (notificationIcon.Init()) { 
     MainForm = ResultForm.GetInstance(); 
     Application.Run(); 
    } 
} 

「ResultForm」是我之前剛剛提到的類,並具有相關的問題,下面的方法:

public static ResultForm GetInstance() { 
    // _this is an attribute from the class. Is used to work always 
    // with just one instance of the classe 
    if (_this==null) 
     _this= new ResultForm(); 

    return _this; 
} 

private ResultForm() { 
    // initialization of the GUI form 
    InitializeComponent(); 

    [...] 

    // watcher for the log files 
    logsWatcher= new FileSystemWatcher(LOGFILES_FOLDER); 
    logsWatcher.Created += new FileSystemEventHandler(NewFile); 
    logsWatcher.EnableRaisingEvents=true; 
    logsWatcher.SynchronizingObject = this; 
} 

private void NewFile (object source, FileSystemEventArgs e) { 
    // make sure the file is of the correct type 
    [...] 
    // perform some analysis on the file 
    [...] 
    // update the contents in the form (some TreeViews and labels) 
    [...] 

    // show the form to the user 
    _this.Show(); 
} 

現在問題來了。如果應用程序啓動,則會分析文件,並且尚未顯示主窗體,但分析完成後將顯示爲「未響應」,但所有操作都已完成。如果之後創建了新文件,它將被成功分析,但表單將保持此「無響應」狀態。但是,如果表單在應用程序啓動之前至少打開過一次(例如,您雙擊圖標,表單就會顯示出來並關閉它(或者將其打開,這並不重要) ),一切都會順利進行。

作爲workaroud,我可以修改主要具有以下兩個線(Run()方法之前),所以任何文件來之前的形式將顯示至少一次:

MainForm.Show(); 
MainForm.Hide(); 

(I隱藏它因爲在執行分析或用戶明確點擊圖標之前它不應該是可見的。) 除此之外,程序沒有區別:所做的工作是相同的,並且表單總是顯示一次一切都完成了。我已經確定調試器在執行期間達到了方法的結束。

如何解決這個問題,而沒有提到的解決方法?

我曾嘗試使用Application.DoEvents()或與this one類似的代碼塊創建分析線程。在最好的情況下,表單正確顯示其所有內容,但保持「未響應」狀態。我也嘗試在方法中保留Show()調用的結果,它告訴我這不是一個重負載的問題,但是我可能做錯了某些事情。

編輯 由於@thecoon要求它,所以我上傳了一個能夠重現問題的小型項目。已經用SharpDevelop完成了,以防你也使用它。 - >http://dl.dropbox.com/u/1153417/test.zip

Main()方法中有一個小的解釋。

+0

你可以在某處上傳一個能夠再現問題的最小樣本嗎? –

+0

您是否嘗試過在單獨的線程中分析日誌?這是防止未定義UI – undefined

+0

凍結的常用方法,正如我在最後一段中提到的,是的,我已經嘗試過,但結果是相同的。 @thecoon,我會嘗試上傳一個樣本,只要我有時間 – Solem

回答

1

我強烈懷疑這是一個線程問題,由您如何啓動您的ResultForm引起的,以及您如何設置FileSystemObjects synchronization object

同步對象有效地選擇線程來編組更新。在這種情況下,它也是您嘗試顯示GUI的線程,所以您可能會遇到一個很好的舊阻塞操作,或者可能只是有大量文件系統事件導致線程上下文切換迅速。

作爲首發,試試這個來代替:

public static void Main(string[] args) { 
    NotificationIcon notificationIcon = new NotificationIcon(); 
    notificationIcon.notifyIcon.Visible = true; 
    if (notificationIcon.Init()) { 
     MainForm = new ResultForm(); 
     Application.Run(MainForm); 
    } 
} 

需要注意的是,我們現在直接名帥ResultForm到UI線程。

並改變ResultForm這樣(有沒有真正的需要爲它是一個單身,太):

public ResultForm() { 
    // initialization of the GUI form 
    InitializeComponent(); 

    [...] 
    this.Load += ResultForm_Load; 
} 


protected void ResultForm_Load(object sender, EventArgs e) 
{ 
    // watcher for the log files 
    logsWatcher= new FileSystemWatcher(LOGFILES_FOLDER); 
    logsWatcher.Created += new FileSystemEventHandler(NewFile); 
    logsWatcher.EnableRaisingEvents=true; 
    //Don't set the synchronization object - now all events from the FileSystemWatcher will be marshalled on a background thread 
    Visible = false; //Hide the form if you want or minimize to tray or similar. 
} 

private void NewFile (object source, FileSystemEventArgs e) { 

    if(InvokeRequired){ 
     //Ensures the file system events are marshalled back to the GUI thread 
     Invoke(new MethodInvoker(() => {NewFile(source, e);})); 
     return; 
    } 

    // make sure the file is of the correct type 
    [...] 
    // perform some analysis on the file 
    [...] 
    // update the contents in the form (some TreeViews and labels) 
    [...] 

    // show the form to the user 
    Show(); //or Visible = true; 
} 

通過不設置在FileSystemWatcher的同步對象是形式,可以確保所有文件系統事件編組將在ThreadPool線程中發生。當發生New事件時,我們只需記住通過檢查InvokeRequired並在必要時調用窗體的Invoke方法來回傳到UI線程。

UPDATE

的主要的原因是,通過調用MainForm.Show直接或經由Application.Run(MainForm的),則推形式到螺紋消息循環上。

如果您使用原始代碼運行應用程序,那麼調用NewFile時,Application.MessageLoop爲false。

如果您使用您的解決方法或顯示應用程序的標準方式,按照我的示例,Application.MessageLoop爲true。

我最好在這裏猜測,表單是死鎖,因爲FileSystemWatcher(它在原始示例中使用表單作爲同步對象,這實際上意味着它在窗體上調用BeginInvoke)。然而,這也可能是其他各種各樣的問題。 Form.Show()實際上掛在方法FPushMessageLoop上 - 它陷入無限循環。

@HansPassant或@HenkHolterman都圍繞這些問題進行了廣泛的討論 - 例如參見https://stackoverflow.com/a/3833002/1073107。請注意,如果您在啓動時顯示啓動畫面或類似信息,則所有初始化都按預期工作,並且NewFile成功完成;總之,它看起來像你顯示在應用程序啓動的東西,如果你想你的過程工作。我不認爲這是一件壞事;例如,用戶可以看到應用程序已啓動並正在托盤中運行,並且您可以確定不會遇到此問題。

+0

聲明之前的那一行非常感謝你的回答,@dash。 我試過你寫的東西。雖然在加載方法中存在「Visible = false」,但無論如何都會使用Application.Run(MainForm)調用來顯示錶單。不幸的是,這不是我正在尋找的原因有兩個:窗口顯示給用戶,當它不應該顯示窗體時,應用程序的行爲與Show()相同, &Hide()方法。儘管如此,'if(InvokeRequiered)'塊以及你解釋的內容很重要,我會牢記它。 – Solem

+0

@Solem首先讓應用程序工作 - 忽略你想要隱藏和顯示錶單的事實 - 你可以稍後再做。首先要做的是建立凍結的來源;如果按照我的答案構建應用程序,並在NewFile中放置斷點或記錄以查看它是否被連續調用。 「//更新表單中的內容(一些TreeViews和標籤)」實際上是做什麼的,例如? – dash

+0

我以爲我曾經寫過,但我錯了。該應用程序與您的解決方案正常工作(就像我的解決方法一樣)。我在「NewFile」開始處放置了一個斷點,並且我看到它被調用了兩次:其中InvokeRequires塊被執行,第二個被調用。我沒有編寫的代碼的其餘部分僅分析日誌文件,更改某些標籤的Text屬性並使用所發現的信息構建樹。這部分已經過測試並正常工作 – Solem