Parallel.For System.OutOfMemoryException - PullRequest
1 голос
/ 07 июня 2010

У нас есть довольно простая программа, которая используется для создания резервных копий.Я пытаюсь распараллелить его, но получаю исключение OutOfMemoryException в AggregateException.Некоторые из исходных папок достаточно велики, и программа не падает примерно на 40 минут после запуска.Я не знаю, с чего начать, поэтому приведенный ниже код является почти точным дампом всего кода, код без структуры каталогов и кода регистрации исключений.Какой-нибудь совет относительно того, где начать искать?

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)));
        });
    }
}

Окно отладки Threads показывает 417 рабочих потоков на момент исключения.

EDIT: Копирование выполняется с одного сервера на другой.Сейчас я пытаюсь запустить код с последним изменением Paralell.ForEach на обычный foreach.

1 Ответ

2 голосов
/ 07 июня 2010

Делая несколько предположений, я еще не получил ответ на комментарий к вашему вопросу.

Я предполагаю, что здесь происходит большое количество рабочих потоков, поскольку действия (действие, являющееся единицей работы, выполняемой на параллельном foreach) занимают больше времени, чем указанное время, поэтому базовый ThreadPool увеличивает количество потоков. Это произойдет, когда ThreadPool будет следовать алгоритму увеличения пула, чтобы новые задачи не блокировались существующими долгосрочными задачами, например, если все мои текущие потоки были заняты в течение полсекунды, я начну добавлять больше потоков в пул. Однако вы столкнетесь с проблемами, если все задачи будут выполняться долго, а добавленные вами новые задачи сделают существующие задачи еще дольше. Вот почему вы, вероятно, видите большое количество рабочих потоков - возможно, из-за перегрузки диска или медленного сетевого ввода-вывода (если задействованы сетевые диски).

Я также предполагаю, что файлы копируются с одного диска на другой, или они копируются из одного места в другое на том же диске. В этом случае добавление темы к проблеме не очень поможет. На исходном и целевом дисках есть только один набор головок, поэтому попытка заставить их делать несколько вещей одновременно может привести к замедлению:

  • Головки дисков будут качаться повсюду.
  • Кэши вашего диска \ ОС могут часто аннулироваться.

Это может не быть большой проблемой для распараллеливания.

Обновление

В ответ на ваш комментарий, если вы получаете ускорение, используя несколько потоков в меньших наборах данных, вы можете поэкспериментировать с уменьшением максимального количества потоков, используемых в вашем параллельном foreach, например,

ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = 2 };

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

Но, пожалуйста, имейте в виду, что смещение диска может свести на нет любые преимущества распараллеливания в общем случае. Поиграйте с ним и оцените свои результаты.

...