Каков правильный синтаксис PLINQ для преобразования этого цикла foreach в параллельное выполнение?

Обновление 20 мая 2011 г., 00:49: foreach по-прежнему на 25% быстрее, чем параллельное решение для моего приложения. И не используйте количество коллекций для максимального параллелизма, используйте что-то близкое к количеству ядер на вашей машине.

=

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

Это основной цикл, который я хочу запустить параллельно.

foreach (ShellObject sf in sfcoll)
    ProcessShellObject(sf, curExeName);

Контекст для этого цикла здесь:

        var curExeName = Path.GetFileName(Assembly.GetEntryAssembly().Location);
        using (ShellFileSystemFolder sfcoll = ShellFileSystemFolder.FromFolderPath(_rootPath))
        {
            //This works, but is not parallel.
            foreach (ShellObject sf in sfcoll)
                ProcessShellObject(sf, curExeName);

            //This doesn't work.
            //My attempt at PLINQ.  This code never calls method ProcessShellObject.

            var query = from sf in sfcoll.AsParallel().WithDegreeOfParallelism(sfcoll.Count())
                        let p = ProcessShellObject(sf, curExeName)
                        select p;
        }

    private String ProcessShellObject(ShellObject sf, string curExeName)
    {
        String unusedReturnValueName = sf.ParsingName
        try
        {
            DesktopItem di = new DesktopItem(sf);
            //Up date DesktopItem stuff
            di.PropertyChanged += new PropertyChangedEventHandler(DesktopItem_PropertyChanged);
            ControlWindowHelper.MainWindow.Dispatcher.Invoke(
                (Action)(() => _desktopItemCollection.Add(di)));
        }
        catch (Exception ex)
        {
        }
        return unusedReturnValueName ;
    }

Спасибо за любую помощь!

+том


person Tom A    schedule 20.05.2011    source источник


Ответы (3)


Ваш объект запроса, созданный с помощью LINQ, является IEnumerable. Он оценивается только в том случае, если вы его перечисляете (например, через цикл foreach):

        var query = from sf in sfcoll.AsParallel().WithDegreeOfParallelism(sfcoll.Count())
                    let p = ProcessShellObject(sf, curExeName)
                    select p;
        foreach(var q in query) 
        {
            // ....
        }
        // or:
        var results = query.ToArray(); // also enumerates query
person m0sa♦    schedule 20.05.2011
comment
@ Том А - что ты имеешь в виду? Мой ответ var results = query.ToList(); не сработал? :) - person Alex Aza; 20.05.2011
comment
@ Том А - на самом деле было бы справедливо сказать, что Джон Скит ответил первым, а также его ответ является наиболее полным. По сути, он дал вам 3 варианта: исправить свой путь, использовать Parallel и использовать Task. - person Alex Aza; 20.05.2011

РЕДАКТИРОВАТЬ: Что касается обновления вашего вопроса. Я не заметил, что задача связана с вводом-выводом, и, предположительно, все файлы находятся на одном (традиционном?) диске. Да, это будет работать медленнее, потому что вы создаете конкуренцию в нераспараллеливаемом ресурсе, заставляя диск искать повсюду.

Задачи, связанные с вводом-выводом, по-прежнему можно эффективно распараллеливать иногда, но это зависит от того, поддается ли распараллеливанию сам ресурс. Например, твердотельный накопитель (который имеет гораздо меньшее время поиска) может полностью изменить характеристики, которые вы видите, или, если вы загружаете данные по сети с нескольких медленных по отдельности серверов, у вас может быть IO -bound, но не на одном канале.


Вы создали запрос, но так и не использовали его. Самый простой способ принудительно использовать все вместе с запросом — это использовать Count() или ToList() или что-то подобное. Однако лучшим подходом было бы использование Parallel.ForEach:

var options = new ParallelOptions { MaxDegreeOfParallelism = sfcoll.Count() };
Parallel.ForEach(sfcoll, options, sf => ProcessShellObject(sf, curExeName));

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

person Jon Skeet    schedule 20.05.2011
comment
Спасибо, Джон! Я наградил вас принятым ответом, хотя вы опоздали на целую минуту, чем первые ответы, потому что вы улучшили ответ с помощью Paralle.l.ForEach. ;) - person Tom A; 20.05.2011
comment
@Tom: я думаю, что мой ответ тоже был первым... у него самый низкий идентификатор. Не то, чтобы это то, на чем должно основываться принятие, хотя :) - person Jon Skeet; 20.05.2011
comment
Извините, Джон, теперь, когда я скомпилировал его, я получаю Невозможно преобразовать лямбда-выражение в тип «System.Threading.Tasks.ParallelOptions», потому что это не тип делегата. - person Tom A; 20.05.2011
comment
@Tom: проверьте перегрузки - я, вероятно, просто поставил аргументы в неправильном порядке. Попробуй с лямдой в конце. - person Jon Skeet; 20.05.2011
comment
А, я вижу, сейчас там написано, что вы ответили 10 минут назад, а два других — 9 минут назад, так что вы были первыми. Я не вижу идентификационный номер в ответах. Я думаю, это помогает иметь дополнительные 3 цифры в репутации. :) спасибо еще раз. - person Tom A; 20.05.2011
comment
@Tom: я отредактировал свой ответ, чтобы исправить вызов ForEach (это был просто порядок аргументов) и прокомментировать ваше обновление. Обратите внимание, что использование ForEach здесь действительно намного чище, чем PLINQ - по сути, вы не пытаетесь выполнить запрос, вы пытаетесь выполнить что-то для каждого элемента в коллекции. Важно использовать наиболее подходящий инструмент для работы, а не работать с естественной направленностью конкретного инструмента, например. путем принудительной оценки с помощью ToList. - person Jon Skeet; 20.05.2011
comment
Джон: Спасибо за обновление. Я оставил принятый ответ как m0sa, потому что это решение действительно сработало, и чтобы поделиться крохами репутации, вы оставляете остальных;). Но ваш ответ теперь намного полнее... я разрываюсь.... - person Tom A; 20.05.2011

Если добавить строку в конце

var results = query.ToList();
person Alex Aza    schedule 20.05.2011