Parallel.ForEach没有在循环中设置所有值

时间:2013-06-08 01:52:00

标签: c# asynchronous task-parallel-library task class-library

我正在为一些员工查询sql数据库。 当我收到这些员工时,我使用Parallel.ForEach循环每个员工。

我循环从数据库中检索到的员工的唯一原因就是扩展了一些我不想混淆数据库的属性。

在这个例子中,我试图在循环中设置当前员工的头像,但是只有三分之一总是被设置,其他员工Avatar都没有被设置为正确的URI。基本上,我正在使用头像的文件名并构建用户文件夹的完整路径。

我在这里做错了什么,每个员工的头像都没有更新到他们目录的完整路径,就像唯一正在设置的那个?并行堆栈有四个深度

我确定我的代码格式不正确。我已经看过那个并行任务了,它在6个线程上创建了4个并行任务。

有人能指出我使用Parallel格式化代码的正确方法吗?

另外,有一件事,如果我从GetEmployees方法中删除return await Task.Run()=>,我会收到一个无法完成任务的错误,因为其他任务先被捕获。

并行就好像只为其中一名员工设置了一个头像。

---呼叫者

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

2 个答案:

答案 0 :(得分:2)

  1. 您对BuildUserFilePath的定义及其用法不一致。该定义清楚地表明它是一个string - 返回方法,而它的用法意味着它返回Task<>
  2. Parallel.ForEach和async不能很好地混合 - 这就是你的bug首先发生的原因。
  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);
                });
            });
        }
    }
    

答案 1 :(得分:1)

此代码中似乎过度使用了async和Task.Run()。例如,您希望从这一部分中获得什么?

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

您已经在整个方法的结果上使用等待,并且您已经使用Parallel.ForEach来获取循环中项目的并行执行,那么await Task.Run()的额外用途是什么?我懂了?如果没有它,代码肯定会更容易理解。

我不清楚你想在这里实现什么。你能描述一下你的目标是什么吗?