Многопоточные циклы каталогов в C # - PullRequest
4 голосов
/ 20 июля 2010

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

doStuff считывает свойства (дату изменения и т. Д.) Из файлов и вставляет их в базу данных sqlite. Я запускаю транзакцию до вызова метода сканирования, чтобы максимально оптимизировать ее.

Ответы, которые дают теорию о том, как это сделать, так же хороши, как и ответы с полным рабочим кодом.

    private static string[] validTypes = { ".x", ".y", ".z", ".etc" };
    public static void scan(string rootDirectory)
    {
        try
        {

            foreach (string dir in Directory.GetDirectories(rootDirectory))
            {

                if (dir.ToLower().IndexOf("$recycle.bin") == -1)
                    scan(dir);
            }

            foreach (string file in Directory.GetFiles(rootDirectory))
            {

                if (!((IList<string>)validTypes).Contains(Path.GetExtension(file)))
                {
                    continue;
                }


                doStuff(file);
            }
        }
        catch (Exception)
        {
        }
    }

Ответы [ 5 ]

5 голосов
/ 20 июля 2010

Предполагая, что doStuff является поточно-ориентированным, и что вам не нужно ждать завершения всего сканирования, вы можете вызвать как doStuff, так и scan в ThreadPool, например:

string path = file;
ThreadPool.QueueUserWorkItem(delegate { doStuff(path); });

Вам нужно создать отдельную локальную переменную, потому что анонимный метод должен был бы перехватить саму переменную file и видеть изменения в ней на протяжении всего цикла. (Другими словами, если ThreadPool выполнил задачу только после того, как цикл продолжился до следующего файла, он обработал бы неправильный файл)

Однако, читая ваш комментарий, основной проблемой здесь является дисковый ввод-вывод, поэтому я подозреваю, что многопоточность мало чем поможет.

Обратите внимание, что Directory.GetFiles будет работать медленно для каталогов с большим количеством файлов. (Так как ему нужно выделить массив для хранения имен файлов)
Если вы используете .Net 4.0, вы можете сделать это быстрее, вызвав вместо этого EnumerateFiles метод , который использует итератор для возврата IEnumerable<string>, который перечисляет каталог при запуске цикла.
Вы также можете избежать рекурсивных вызовов scan с помощью любого метода, передав параметр SearchOption, например:

foreach (string file in Directory.EnumerateFiles(rootDirectory, "*", SearchOption.AllDirectories))

Это будет рекурсивно сканировать все подкаталоги, поэтому вам понадобится всего один цикл foreach.
Обратите внимание, что это усугубит проблемы с производительностью GetFiles, поэтому вы можете избежать этой предварительной версии 4.0.

2 голосов
/ 20 июля 2010

Использование многопоточности в операциях ввода-вывода обычно является плохим вызовом *.У вас может быть несколько процессоров или процессор с несколькими ядрами;но обычно ваш жесткий диск не может читать или записывать несколько файлов одновременно .Подобные вещи, как правило, должны быть сериализованы.

Тем не менее, рекомендуется выполнять такую ​​работу в потоке, отдельном от вашего потока пользовательского интерфейса.Таким образом, пользовательский интерфейс остается отзывчивым, пока ваше приложение выполняет тяжелую работу.

* Я предполагаю, что ваши методы scan и doStuff фактически читают и / или записывают данные на жесткий диск.Если это не так, распараллеливание этого кода может иметь смысл.

1 голос
/ 20 июля 2010

Кстати, нет необходимости приводить validTypes к IList<string>, потому что массивы реализуют IEnumerable<T> в .net 3.5 +.

Во-вторых, validTypes может быть лучше реализовано как HashSet , предоставляя вам O (1) поиск вместо O (n) с Contains. Тем не менее, это, вероятно, не повлияет на производительность в этом случае, поскольку ваше приложение связано с вводом-выводом, как указано в других ответах.

1 голос
/ 20 июля 2010

Что именно делают doStuff и scan? Если бы они не сильно загружали процессор, я бы подумал, что доступ к диску будет узким местом и что если что-то делает его многопоточным, это может быть медленнее.

0 голосов
/ 21 июля 2010

Спасибо всем, кто откликнулся.В итоге я получил

        foreach (string file in Directory.EnumerateFiles(rootDirectory, "*", SearchOption.AllDirectories))
        {
            if (!((IList<string>)validTypes).Contains(Path.GetExtension(file)))
            {
                continue;
            }
            string path = file;
            ThreadPool.QueueUserWorkItem(delegate { doStuff(path); });
        }

Это длилось около 2 минут по сравнению с несколькими часами, которые он занимал раньше.Я думаю, что большая часть отставания была в базе данных, а не в файле ввода-вывода.

Большое спасибо всем!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...