【问题标题】:out of memory exception while using multithread in C#在 C# 中使用多线程时出现内存不足异常
【发布时间】:2013-07-25 15:28:29
【问题描述】:

我是线程新手,在这个问题上卡在这里。我有一个使用多线程的应用程序。我有一个在 ftp 服务器上上传数千张图像的功能。对于每个图像,我正在创建一个新线程。该线程调用函数连接ftp服务器,上传文件,上传成功返回布尔值。

我的问题是,由于我要上传数千张图片,并且每张图片都在创建自己的线程,所以在一段时间后我遇到内存不足异常并且应用程序冻结。

我的代码如下:

public Int16 UploadFiles(string[] files)
{
     foreach (var fileName in files)
        {
            if (UploadFile(fileName))
            {
                strLogText += "\r\n\tFile: " + fileName + " downloaded.";
            }
        }
}

private bool UploadFile(string fileName)
    {
        var blnDownload = false;
        var thread = new Thread(() => DownLoadFileNow(fileName, out blnDownload)) {IsBackground = true};
        thread.Start();
        return blnDownload;
    }

    private void DownLoadFileNow(string fileName, out bool blnDownload)
    {
        //Get file path and name on source ftp server
        var srcFolder = GetSrcFolderName(fileName);

        //Get Local Folder Name for downloaded files
        var trgFolder = GetLocalFolder(fileName, "D");

        var reqFtp =
            (FtpWebRequest) WebRequest.Create(new Uri("ftp://" + _strSourceFtpurl + srcFolder + "/" + fileName));
        reqFtp.Method = WebRequestMethods.Ftp.DownloadFile;
        reqFtp.UseBinary = true;
        reqFtp.Credentials = new NetworkCredential(_strSourceFtpUser, _strSourceFtpPassword);
        var outputStream = new FileStream(trgFolder + "\\" + fileName, FileMode.Create);

        try
        {
            var response = (FtpWebResponse) reqFtp.GetResponse();
            var ftpStream = response.GetResponseStream();
            const int bufferSize = 2048;
            var buffer = new byte[bufferSize];

            if (ftpStream != null)
            {
                int readCount = ftpStream.Read(buffer, 0, bufferSize);
                while (readCount > 0)
                {
                    outputStream.Write(buffer, 0, readCount);
                    readCount = ftpStream.Read(buffer, 0, bufferSize);
                }

                ftpStream.Close();
            }
            response.Close();
            blnDownload = true;
        }
        catch (WebException ex)
        {
            _log.WriteLog("Error in Downloading File (" + fileName + "):\r\n\t" + ex.Message, "");
            //Delete newly created file from local system
            outputStream.Close();
            if (File.Exists(trgFolder + "/" + fileName))
                File.Delete(trgFolder + "/" + fileName);
        }
        catch (Exception ex)
        {
            _log.WriteLog("Error in Downloading File (" + fileName + "):\r\n\t" + ex.Message, "");
        }
        finally
        {
            outputStream.Close();
            outputStream.Dispose();
        }
        blnDownload = false;
    }

请帮助并告诉我如何限制创建的线程数,以便一次运行的线程不超过 10-20 个。

【问题讨论】:

  • a thread uses 1MB of memory;这就是你得到错误的原因。您需要改用 TPL,如下面的答案所示。
  • 创建一种队列,限制一次可以下载多少东西。也许尝试使用任务而不是线程,这样您就可以知道它们何时完成
  • 任何使用超过 (8 * logical_processors) 线程的 Windows 进程都将无法执行或扩展。例如,具有使用超过 8 个线程来处理请求(例如套接字接受)的服务应用程序的单 CPU 系统,将很快让盒子屈服。
  • @DanielBullington - 什么?我从 Firefox 发布这个 - 57 个线程。我的 i7 有 4/8 核。 Kaspersky AV 有 92 个线程。 NT 内核和系统有 257 个线程。你从哪里得到“超过(8 * logical_processors)”的谬误?
  • OP 的问题是不断创建/终止/销毁线程,而不是将任务提交到线程池。对于大部分在 I/O 上被阻塞的联网任务,池中 64 个线程并不是不合理的。

标签: c# multithreading out-of-memory


【解决方案1】:

你不能创建这么多线程。一种替代方法是使用 parrelle 扩展。

public void UploadFiles(string[] files)
{
    files.AsParallel().ForAll(fileName =>
    {
        if (UploadFile(fileName))
        {
            strLogText += "\r\n\tFile: " + fileName + " downloaded.";
        }
    });
}

【讨论】:

    【解决方案2】:

    尝试将 UploadFiles(string[] files) 中的 foreach 替换为调用 DownloadFileNow 而不是 UploadFile(String file) 方法的 Parallel.ForEach()。

    Parallel.Foreach 将从线程池中提取线程,这是您想要的,并且会简化您的代码。

     Parallel.ForEach(files, fileName =>
            DownloadFileNow(fileName);
            strLogText += "\r\n\tFile: " + fileName + " downloaded.";
     );
    

    【讨论】:

    • 谢谢,我现在得到“AggregateException 未处理”。知道如何解决这个问题吗?
    • 我敢打赌这与以异步方式更新 strLogText 有关。你用 strLogText 做什么?
    • 我用来在函数末尾添加日志文件,以了解上传了多少文件和哪些文件。我不是每次都添加,而是将它们添加到字符串中,并且在每个循环结束后,我添加到日志文件中。
    • 我为 strLogText 注释了该行,但仍然可以看到相同的错误。
    • 它是否在 AggregateException 堆栈跟踪中为您提供了行号?
    【解决方案3】:

    正如其他人所指出的,您不应该创建那么多线程。现在...Parallel.ForEach() 会给你很好的语法糖,你应该走这条路!

    只是想指出,您需要将线程池作为一个概念来查找。看,让大量线程并行运行是没有意义的。对于每个这样的任务,都有一个最佳线程数,超过这个数,整个线程开销实际上会开始减慢你的速度。或者,在你的情况下,用尽你所有的内存。

    如果您将任务视为放在桌子(文件夹)上的一堆照片,而线程则视为员工为您跑腿,那么让一名员工从办公桌上取一张照片,将其放入信封并带上它到邮局,对于每张照片,都要花很长时间。因此,您雇用了另一名员工。还有一个。但是,一旦你接触到一定数量的照片盖章者,他们就会开始互相干扰。他们在办公桌前排队。他们在邮局前排队。整个信封的情况对负责办公用品的可怜的玛丽来说是痛苦的——他们也在她面前排队!因此,找出最佳员工数量(您可以进行测试或只是猜测一下)并分配他们重复该任务,直到桌子空无一人...

    这是一个经常出现的主/从模式。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多