线程停止工作

时间:2009-01-12 06:43:57

标签: c# multithreading backgroundworker

我们有一个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文件。

5 个答案:

答案 0 :(得分:4)

如果我正确理解你,这个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()。我还建议使用数据库来存储日志消息而不是文件,以便锁定问题不会使问题复杂化。

我知道这不是一个答案,你可能仍会遇到问题,因为我无法确定原因可能是什么。但是我对一点设计重组很有信心,你可以更好地找到问题的根源。

答案 1 :(得分:2)

我建议在catch块中放置任何可能出错的地方(特别是与FTP服务器断开连接的位)。另外,在你做了其他任何事情之前,在你发现异常之后记录一些事情 - 这样你就更有可能知道日志记录是否由于某种原因而中途退出。

此外,在while循环的末尾添加一条日志消息,以便您可以判断它是否“正常”完成。

答案 2 :(得分:0)

我建议在问题重现并让自己成为挂起转储时使用adplus。分析Windbg和SoS。

这是在Winforms应用程序中吗?也许ISynchronizeInvoke实现正在挂起。这是作为交互式用户运行吗?

答案 3 :(得分:0)

Rupert:我在catch块之后添加了ftp.Disconnect()并再次启动它。我已经检查了原始应用程序,我们在重新连接之前断开连接,所以虽然它可以影响问题,但我认为它不会导致它。 没有其他线程访问它,所以没有问题。

乔恩:我会的,谢谢你的建议。

JD:它是一个Windows应用程序,在选择延迟和FTP连接数据后,用户不提供任何输入。我将研究ISynchronizeInvoke

答案 4 :(得分: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服务器通信,因此在设置之后你应该执行一个thread.Join()来等待它退出。

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