2009-01-12 70 views
3

我們有一個C#應用程序,它連接到FTP服務器,下載一些文件,斷開連接並在一段時間後(由用戶通過UI選擇)重新連接並重復該過程。我們使用BackgroundWorker實現了這個功能,但我們注意到,在運行更長時間之後,程序停止記錄其操作,無論是在UI還是日誌文件中。 那時,沒有文件要下載,所以我們上傳了一些文件,它恢復了活動,就好像什麼都沒發生過一樣。線程停止工作

問題是,普通用戶無法知道該程序仍在工作,所以我們決定使用自己的線程來實現它。我們做了一個更簡單的程序,排除其他任何問題,並且這個程序只連接到FTP並斷開連接。它停止顯示消息,就像BackgroundWorker一樣(2小時後一次,22小時後一次,沒有任何我們可以找到的模式,並且在沒有其他任何事情的計算機上)。

DoFTPWork += new DoFTPWorkDelegate(WriteFTPMessage); 

FTPWorkThread = new Thread(new ParameterizedThreadStart(Process)); 

//seData is the FTP login info 
FTPWorkThread.Start(seData); 

和FTP方法是:

private void Process(object seData1) 
{ 
    seData = (SEData)seData1; 
    while (!stopped) 
    { 
     try 
     { 
      ftp = null; 
      ftp = new FTP_Client(); 

      if (ftp.IsConnected) 
      { 
       logMessages += DateTime.Now + "\t" + "info" + "\t" + "Ftp disconnected from " + seData.host + "\r\n"; 
       ftp.Disconnect(); 
      } 

      ftp.Connect(seData.host, 21); 
      ftp.Authenticate(seData.userName, seData.password); 
      logMessages += DateTime.Now + "\t" + "info" + "\t" + "Ftp connected to " + seData.host + "\r\n"; 

      error = false; 
      logMessages += DateTime.Now + "\t" + "info" + "\t" + "Trying to reconnect in 5 seconds\r\n"; 
      System.Threading.Thread.Sleep(5000); 
      SlaveEventArgs ev = new SlaveEventArgs(); 
      ev.Message = logMessages; 
      txtLog.Invoke(DoFTPWork, ev); 
      System.Threading.Thread.Sleep(200); 
      logMessages = ""; 
     } 

     catch (Exception ex) 
     { 
      logMessages = ""; 
      if (ftp.IsConnected) 
      { 
       ftp.Disconnect(); 
      } 
      ftp.Dispose(); 
      logMessages += DateTime.Now + "\t" + "ERR" + "\t" + ex.Message + "\r\n"; 

      logMessages += DateTime.Now + "\t" + "info" + "\t" + "Trying to reconnect in 5 seconds\r\n"; 
      SlaveEventArgs ev = new SlaveEventArgs(); 
      ev.Message = logMessages; 
      txtLog.Invoke(DoFTPWork, ev); 
      System.Threading.Thread.Sleep(5 * 1000); 
      error = true; 
     } 
    } 
} 

WriteFTPMessage在TextBox顯示消息並在原始程序中寫爲.txt文件。

回答

3

如果我正確理解你,這while(!stopped)循環是運行幾個小時的循環?如果是這樣的話,你在哪裏終止你的ftp連接?在你發佈的代碼中關閉它的唯一時間是如果引發異常,否則你只需解引用對象並創建一個新的,這是一個非常嚴重的資源泄漏,並且如果不引起這個問題的話至少會造成問題。

此外,似乎ftp是全球訪問。您是否正在使用其他線程訪問它?對象線程是否安全?

編輯:

我在這裏看到的最大的問題就是設計。不是說我試圖在你身上或任何東西上打包,但是你有各種混雜的操作。線程,日誌和ftp訪問代碼都在同一個函數中。

我會推薦的是重構你的程序。創建一個類似以下的方法:

// Called by thread 
void MyThreadOperation() 
{ 
    while(!stopped) 
    { 
     // This is poor design in terms of performance. 
     // Consider using a ResetEvent instead. 
     Thread.Sleep(5000); 

     try 
     { 
     doFTPDownload(); 
     } 
     catch(Exception ex) 
     { 
     logMessage(ex.ToString()); 
     } 
    } 
} 

doFTPDownload()應該是自包含的。 FTP對象應該在函數被調用時創建並打開,並且在它完成之前應該關閉。同樣的概念也應該適用於logMessage()。我還建議使用數據庫來存儲日誌消息而不是文件,以便鎖定問題不會使問題複雜化。

我知道這不是一個答案,因爲你可能仍然遇到問題,因爲我不能肯定地說可能是什麼原因。不過,我有信心進行一點設計重組,您將能更好地追蹤問題的根源。

+0

+1爲資源泄漏部分。如果有很多活動連接,可能很容易達到(可能很小)限制並導致掛起。 – 2009-01-12 07:38:03

+0

我們進行了一次重組,此外,我們每隔100行就清除UI中的日誌文本框,並且客戶沒有再報告任何凍結。 – Rox 2009-11-20 07:12:05

2

我會建議把任何可能出現錯誤的東西放在它自己的try/catch塊中的catch塊(特別是與FTP服務器斷開的位)。另外,在你做任何其他事情之前,一旦你發現異常就記錄下來,這樣你就更有可能知道日誌記錄是否由於某種原因中途死亡。

另外,在while循環的末尾添加一條日誌消息,以便您可以判斷它是否「正常」完成。

+0

+1首先進行測井。 – 2009-01-12 07:59:03

0

我會建議使用adplus,當問題重現並讓自己掛起轉儲。分析Windbg和SoS。

這是在Winforms應用程序?也許ISynchronizeInvoke實現掛起。這是以交互式用戶身份運行嗎?

0

Rupert:我在catch塊後添加了ftp.Disconnect(),並重新啓動它。我已經檢查了原始應用程序,並在重新連接之前斷開連接,所以雖然它可以影響問題,但我認爲它不會導致它。 沒有其他線程可以訪問它,所以這裏沒有問題。

喬恩:我會的,謝謝你的建議。

JD:這是一個Windows應用程序,在選擇延遲和FTP連接數據後,用戶不會給出任何輸入。我會研究ISynchronizeInvoke

0

我認爲你必須努力使它更安全。您有很多共享字段:ftp, logMessages, error

例如這一部分:

 ev.Message = logMessages; 
     txtLog.Invoke(DoFTPWork, ev); 
     System.Threading.Thread.Sleep(200); 
     logMessages = ""; 

聽起來好像是你試圖通過睡覺,穿越你的手指,你睡夠解決多線程的問題...

你能解決這個通過:

 ev.Message = logMessages.Clone(); 
     txtLog.Invoke(DoFTPWork, ev); 

或使用不同的溝通方式。

而不是停止的布爾值,你可以使用ManualResetEvent,這是一個線程安全的通信方法。而對於錯誤,你可以使用相同的,或信號量。

有關ManualResetEvent的好處是您可以使用它來睡眠您的線程而不會完全鎖定它。如果我沒有弄錯,在睡眠時停止線程的唯一方法就是調用一個線程.Abort。如果您使用的ManualResetEvent,你可以做到以下幾點:

if (!shouldStop.WaitOne(5000)) 
{ 
    // do thread stuff 
} 
else 
{ 
    // do cleanup stuff and exit thread. 
} 

的好處是,你會說我想知道,如果該事件被信號或沒有,但我會等待5秒鐘就發出信號或否則我會繼續未發出信號。

因此,如果您的應用程序在睡眠3秒後決定退出,它只需執行一次shouldStop.Set()並且該線程將停止。線程仍然可能與ftp服務器進行通信,因此在設置之後,應該執行一個線程.Join()等待它退出。

我不是說你的問題與我的建議有關,如果不是,我只是試圖幫助減少可能的原因。