2009-01-12 76 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()等待它退出。

我不是说你的问题与我的建议有关,如果不是,我只是试图帮助减少可能的原因。