Какой правильный синтаксис PLINQ для преобразования этого цикла foreach в параллельное выполнение? - PullRequest
1 голос
/ 20 мая 2011

Обновление 2011-05-20 12:49 AM: 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 ;
    }

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

+ * Том 1015 *

Ответы [ 3 ]

7 голосов
/ 20 мая 2011

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

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


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

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

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

1 голос
/ 20 мая 2011

Ваш объект запроса, созданный через 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
1 голос
/ 20 мая 2011

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

var results = query.ToList();
...