Parallel.For System.OutOfMemoryException

时间:2010-06-06 20:48:54

标签: c#-3.0 parallel-processing out-of-memory .net

我们有一个非常简单的程序用于创建备份。我正在尝试并行化它,但在AggregateException中得到OutOfMemoryException。一些源文件夹非常大,程序启动后大约40分钟不会崩溃。我不知道从哪里开始查找所以下面的代码是所有代码的近乎精确的转储代码sans目录结构和异常日志记录代码。关于从哪里开始寻找任何建议?

using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;

namespace SelfBackup
{
class Program
{

static readonly string[] saSrc = { 
    "\\src\\dir1\\",
    //...
    "\\src\\dirN\\", //this folder is over 6 GB
};
static readonly string[] saDest = { 
    "\\dest\\dir1\\",
    //...
    "\\dest\\dirN\\",
};

static void Main(string[] args)
{
Parallel.For(0, saDest.Length, i =>
{
    try
    {
        if (Directory.Exists(sDest))
        {
            //Delete directory first so old stuff gets cleaned up
            Directory.Delete(sDest, true);
        }

        //recursive function 
        clsCopyDirectory.copyDirectory(saSrc[i], sDest);
    }
    catch (Exception e)
    {
        //standard error logging
        CL.EmailError();
    }
});
}
}

///////////////////////////////////////
using System.IO;
using System.Threading.Tasks;

namespace SelfBackup
{
static class clsCopyDirectory
{
    static public void copyDirectory(string Src, string Dst)
    {
        Directory.CreateDirectory(Dst);

        /* Copy all the files in the folder
           If and when .NET 4.0 is installed, change 
           Directory.GetFiles to Directory.Enumerate files for 
           slightly better performance.*/
        Parallel.ForEach<string>(Directory.GetFiles(Src), file =>
        {
            /* An exception thrown here may be arbitrarily deep into 
               this recursive function there's also a good chance that
               if one copy fails here, so too will other files in the 
               same directory, so we don't want to spam out hundreds of 
               error e-mails but we don't want to abort all together. 
               Instead, the best solution is probably to throw back up 
               to the original caller of copy directory an move on to 
               the next Src/Dst pair by not catching any possible
               exception here.*/
            File.Copy(file, //src
                      Path.Combine(Dst, Path.GetFileName(file)), //dest
                      true);//bool overwrite
        });

        //Call this function again for every directory in the folder.
        Parallel.ForEach(Directory.GetDirectories(Src), dir =>
        {
            copyDirectory(dir, Path.Combine(Dst, Path.GetFileName(dir)));
        });
    }
}

“线程”调试窗口在异常时显示417个工作线程。

编辑:复制是从一台服务器到另一台服务器。我现在正在尝试运行代码,最后一个Paralell.ForEach更改为常规foreach。

1 个答案:

答案 0 :(得分:2)

在这里做一些猜测,因为我还没有从你的问题的评论中得到反馈。

我猜测大量的工作线程正在这里作为动作(一个动作是在并行foreach上执行的工作单元)花费的时间超过指定的时间,因此底层的ThreadPool正在增长线程数。这将发生在ThreadPool遵循增长池的算法时,以便现有长期运行的任务不会阻止新任务,例如如果我所有的当前线程都忙了半秒钟,我将开始向池中添加更多线程。但是,如果所有任务都在长时间运行,并且您添加的新任务将使现有任务运行得更长,那么您将遇到麻烦。这就是为什么您可能会看到大量工作线程 - 可能是因为磁盘抖动或网络IO速度慢(如果涉及网络驱动器)。

我也猜测文件正在从一个磁盘复制到另一个磁盘,或者它们正在同一磁盘上从一个位置复制到另一个位置。在这种情况下,为问题添加线程并不会有太大帮助。源磁盘和目标磁盘只有一组磁头,所以试图让它们同时执行多项操作可能会减慢速度:

  • 磁盘头将遍布整个地方。
  • 您的磁盘\操作系统缓存可能经常失效。

这可能不是并行化的一个大问题。

<强>更新

在回答您的评论时,如果您在较小的数据集上使用多个线程加速,那么您可以尝试降低并行foreach中使用的最大线程数,例如

ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = 2 };

Parallel.ForEach(Directory.GetFiles(Src), options, file =>
{
    //Do stuff
});

但请注意,在一般情况下,磁盘颠簸可能会抵消并行化带来的任何好处。玩它并测量你的结果。

相关问题