Parallel.ForEach не устанавливает все значения в цикле

Я запрашиваю базу данных sql для некоторых сотрудников. Когда я получаю этих сотрудников, я зацикливаю каждого из них, используя Parallel.ForEach.

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

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

Что я делаю неправильно здесь, когда аватар каждого сотрудника не обновляется с указанием полного пути к их каталогу, например, единственного, который устанавливается? Параллельный стек и есть в глубокой четверке

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

Может ли кто-нибудь указать мне правильный способ форматирования кода для использования Parallel?

Кроме того, во-первых, если я удаляю return await Task.Run()=> из метода GetEmployees, я получаю сообщение об ошибке «Не могу завершить задачу, потому что какая-то другая задача вылавливается первой».

Параллель действует так, как будто устанавливает только один из Аватаров для одного из сотрудников.

---Абонент

   public async static Task<List<uspGetEmployees_Result>> GetEmployess(int professionalID, int startIndex, int pageSize, string where, string equals)
{
    var httpCurrent = HttpContext.Current;

    return await Task.Run(() =>
        {
            List<uspGetEmployees_Result> emps = null;
            try
            {
                using (AFCCInc_ComEntities db = new AFCCInc_ComEntities())
                {
                    var tempEmps = db.uspGetEmployees(professionalID, startIndex, pageSize, where, equals);
                    if (tempEmps != null)
                    {
                        emps = tempEmps.ToList<uspGetEmployees_Result>();

                        Parallel.ForEach<uspGetEmployees_Result>(
                             emps,
                            async (e) =>
                            {
                                e.Avatar = await Task.Run(() => BuildUserFilePath(e.Avatar, e.UserId, httpCurrent, true));
                            }
                         );
                    };
                }
            }
            catch (SqlException ex)
            {
                throw ex;
            };
            return emps;
        });
}

--Вызов

    static string BuildUserFilePath(object fileName, object userProviderKey, HttpContext context, bool resolveForClient = false)
{
    return string.Format("{0}/{1}/{2}",
                                   resolveForClient ?
                                   context.Request.Url.AbsoluteUri.Replace(context.Request.Url.PathAndQuery, "") : "~",
                                   _membersFolderPath + AFCCIncSecurity.Folder.GetEncryptNameForSiteMemberFolder(userProviderKey.ToString(), _cryptPassword),
                                   fileName.ToString());
}

----------------------------------------Редактировать--------- ---------------------------

Окончательный код, который я использую с помощью всех. Большое спасибо!

public async static Task<List<uspGetEmployees_Result>> GetEmployess(int professionalID, int startIndex, int pageSize, string where, string equals)
    {
        var httpCurrent = HttpContext.Current;
        List<uspGetEmployees_Result> emps = null;

        using (AFCCInc_ComEntities db = new AFCCInc_ComEntities())
        {

            emps = await Task.Run(() => (db.uspGetEmployees(professionalID, startIndex, pageSize, where, equals) ?? Enumerable.Empty<uspGetEmployees_Result>()).ToList());

            if (emps.Count() == 0) { return null; }
            int skip = 0;
            while (true)
            {
                // Do parallel processing in "waves".
                var tasks = emps
                      .Take(Environment.ProcessorCount)
                      .Select(e => Task.Run(() => e.Avatar = BuildUserFilePath(e.Avatar, e.UserId, httpCurrent, true))) // No await here - we just want the tasks.
                      .Skip(skip)
                      .ToArray();

                if (tasks.Length == 0) { break; }

                skip += Environment.ProcessorCount;
                await Task.WhenAll(tasks);
            };
        }
        return emps;
    }

person Filling The Stack is What I DO    schedule 08.06.2013    source источник
comment
Я нашел эту строку довольно забавной: e.Avatar = Task.Run(async () => await BuildUserFilePath(e.Avatar, e.UserId, httpCurrent, true)).Result;. См. последние пару абзацев в следующем сообщении в блоге Стивена Туба: blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx   -  person Kirill Shlenskiy    schedule 08.06.2013


Ответы (2)


  1. Ваше определение BuildUserFilePath и его использование противоречивы. В определении четко указано, что это метод, возвращающий string, тогда как его использование подразумевает, что он возвращает Task<>.
  2. Parallel.ForEach и async не очень хорошо сочетаются - в первую очередь поэтому возникла ваша ошибка.
  3. Несвязанный, но все же стоит отметить: ваш try/catch является избыточным, поскольку все, что он делает, это повторно выбрасывает исходный SqlException (и даже это не очень хорошо, потому что вы в конечном итоге потеряете трассировку стека).
  4. Вы действительно, действительно хотите вернуть null?

    public async static Task<List<uspGetEmployees_Result>> GetEmployess(int professionalID, int startIndex, int pageSize, string where, string equals)
    {
        var httpCurrent = HttpContext.Current;
    
        // Most of these operations are unlikely to be time-consuming,
        // so why await the whole thing?
        using (AFCCInc_ComEntities db = new AFCCInc_ComEntities())
        {
            // I don't really know what exactly uspGetEmployees returns
            // and, if it's an IEnumerable, whether it yields its elements lazily.
            // The fact that it can be null, however, bothers me, so I'll sidestep it.
            List<uspGetEmployees_Result> emps = await Task.Run(() =>
                (db.uspGetEmployees(professionalID, startIndex, pageSize, where, equals) ?? Enumerable.Empty<uspGetEmployees_Result>()).ToList()
            );
    
            // I'm assuming that BuildUserFilePath returns string - no async.
            await Task.Run(() =>
            {
                Parallel.ForEach(emps, e =>
                {
                    // NO async/await within the ForEach delegate body.
                    e.Avatar = BuildUserFilePath(e.Avatar, e.UserId, httpCurrent, true);
                });
            });
        }
    }
    
person Kirill Shlenskiy    schedule 08.06.2013
comment
Тот факт, что uspGetEmployees() может возвращать null, должен быть зафиксирован в этом методе, а не везде, где он используется. И GetEmployess() не должен повторять ту же ошибку. - person svick; 08.06.2013
comment
И вариант 3 не имеет для меня особого смысла, единственная блокировка, которую он избегает, - это основной поток, ожидающий завершения остальных потоков, которых не должно быть так много. И на самом деле он будет бесконечно обрабатывать одних и тех же сотрудников n в начале сбора, потому что вы не меняете emps. Для лучшего способа см. эту статью. или поток данных TPL. - person svick; 08.06.2013
comment
Привет, ребята, это действительно потрясающая информация, которой вы все нашли время поделиться, и я благодарен! Это не для слабонервных это точно. Есть вопрос, я использую этот 3-й вариант, и это то, что нужно посмотреть в отладчике, потоке и параллельных средствах просмотра. ОДНА вещь, чтобы выйти из состояния while(true), должен быть перерыв; вместо продолжения? правильно или неправильно? и самое главное здесь то, что task.length никогда не == 0, поэтому он принимает .Take(Enviroment.PorcessorCount). Когда while true запрашивает emps, он всегда начинается с самого начала. Что исправить? - person Filling The Stack is What I DO; 08.06.2013
comment
@svick, ты прав в обоих случаях. Я исправил третий фрагмент кода, но в итоге он получился таким уродливым, что я просто удалил его. Я также удалил вариант PLINQ, так как он не подходит для этой задачи и не добавляет большой ценности. Что касается проверки null, я оставляю это AlumCloud.Com, чтобы разобраться. - person Kirill Shlenskiy; 09.06.2013
comment
@AlumCloud.Com этот фрагмент кода изначально был взят из моего собственного проекта, в котором используется (достаточно забавно) метод BlockingCollection и его метод GetConsumingEnumerable, который не будет страдать от той же проблемы - то, что я потерял из виду, пока писал свой ответ. Пожалуйста, примите мои извинения. Я привел в порядок код, чтобы удалить две опции, которых, возможно, не должно было быть с самого начала. - person Kirill Shlenskiy; 09.06.2013

Кажется, что в этом коде чрезмерно используются async и Task.Run(). Например, чего вы надеетесь добиться от этого сегмента?

  Parallel.ForEach<uspGetEmployees_Result>(
                             emps,
                            async (e) =>
                            {
                                e.Avatar = await Task.Run(() => BuildUserFilePath(e.Avatar, e.UserId, httpCurrent, true));
                            }
                         );

Вы уже используете await для результата всего метода, и вы использовали Parallel.ForEach для параллельного выполнения элементов в вашем цикле, так что же дает вам дополнительное использование await Task.Run()? Коду, безусловно, было бы намного легче следовать без него.

Мне непонятно, чего вы здесь добиваетесь. Можете ли вы описать, каковы ваши цели для этого метода?

person Tim Long    schedule 08.06.2013
comment
Да, чувак, я думаю, что нужно поставить async или Task.Run на каждый вызываемый метод, и я думаю, что то, что ты пытаешься мне сказать, если я оберну все тело с помощью return await Task.Run() => я делаю не нужно добавлять какие-либо другие ключевые слова асинхронного ожидания. Итак, когда я вызываю асинхронный метод Task‹string› BuildUserFilePath(), нет причин делать что-либо, кроме добавления .Result в конец вызывающего местоположения, потому что текущий код уже находится в асинхронном режиме.? Я снова обновил свой пост, сообщите, пожалуйста, правильно ли я его структурировал и действительно ли он работает асинхронно? - person Filling The Stack is What I DO; 08.06.2013
comment
Неважно, я нашел .Skip() в выражении. Я обновил основной код, чтобы отразить это. - person Filling The Stack is What I DO; 08.06.2013