1

I am querying a sql data base for some employees. When i receive these employees I and looping each one using the Parallel.ForEach.

The only reason I'm looping he employees that was retrieved from the database is so expand a few of the properties that I do not want to clutter up the data base with.

Never-the-less in this example I am attempting to set the Avatar for the current employee in the loop, but only one out of three always gets set, none of the other employees Avatar ever gets set to their correct URI. Basically, I'm taking the file-name of the avatar and building the full path to the users folder.

What am I doing wrong here to where each employees Avatar is not updated with the full path to their directory, like the only one that is being set? Parallel stack and there is in deep four

I'm sure I've got the code formatted incorrectly. I've looked at that Parallel Task and it does in deep create 4 Parallel Task on 6 Threads.

Can someone point out to me the correct way to format the code to use Parallel?

Also, one thing, if I remove the return await Task.Run()=> from the GetEmployees method I get an error of cannot finish task because some other task fished first.

The Parallel is acting as if it is only setting one of the Avatars for one of the employees.

---Caller

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

--Callee

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

----------------------------------------Edit------------------------------------

The final code that I'm using with everyone's help. Thanks so much!

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;
    }
  • I found this line quite humorous: `e.Avatar = Task.Run(async () => await BuildUserFilePath(e.Avatar, e.UserId, httpCurrent, true)).Result;`. See last couple of paragraphs in the following Stephen Toub's blog post: http://blogs.msdn.com/b/pfxteam/archive/2012/02/08/10265476.aspx – Kirill Shlenskiy Jun 08 '13 at 02:48

2 Answers2

2
  1. Your definition of BuildUserFilePath and its usage are inconsistent. The definition clearly states that it's a string-returning method, whereas its usage implies that it returns a Task<>.
  2. Parallel.ForEach and async don't mix very well - that's the reason your bug happened in the first place.
  3. Unrelated but still worth noting: your try/catch is redundant as all it does is rethrow the original SqlException (and even that it doesn't do very well because you'll end up losing the stack trace).
  4. Do you really, really want to return 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);
                });
            });
        }
    }
    
Kirill Shlenskiy
  • 9,367
  • 27
  • 39
  • 1
    The fact that `uspGetEmployees()` can return `null` should be fixed in that method, not wherever it's used. And `GetEmployess()` should not repeat the same mistake. – svick Jun 08 '13 at 13:30
  • 1
    And option 3 doesn't make much sense to me, the only blocking it avoids is of the primary thread waiting for the remaining threads to complete, which shouldn't be that much. And it will actually process the same *n* employees on the start of the collection infinitely, because you don't change `emps`. For a better way, see [this article](http://blogs.msdn.com/b/pfxteam/archive/2012/03/05/10278165.aspx) or TPL Dataflow. – svick Jun 08 '13 at 13:39
  • Hey guys this is some really awesome information that you've all taken the time to share, and I am grateful! This is not for the weak hearted that's for sure. Got a question, I'm using that 3rd option and it is something to look at in the debugger, thread, and parallel viewers. ONE THING,In order to get out of the while(true) is should be break; in the place of continue? right or wrong? and the most important thing here is that the task.length never ==0, So, it's taking the .Take(Enviroment.PorcessorCount). When the while true query the emps it always starts from the beginning. Whats the fix? – Filling The Stack is What I DO Jun 08 '13 at 19:19
  • @svick, you are right on both accounts. I fixed up the third bit of code but ended up with it being so ugly, I just took it out. I also removed the PLINQ option as it is not the right tool for the task and doesn't add much value. As for the `null` check - I'm leaving that to AlumCloud.Com to sort out. – Kirill Shlenskiy Jun 09 '13 at 00:13
  • @AlumCloud.Com that bit of code was originally taken from my own project which uses a (funny enough) `BlockingCollection` and its `GetConsumingEnumerable` method, which wouldn't suffer from the same problem - something I lost track of while I was writing my answer. Please accept my apologies. I have tidied up the code to remove the two options that perhaps shouldn't have been there to begin with. – Kirill Shlenskiy Jun 09 '13 at 00:16
1

There seems to be over-use of async and Task.Run() in this code. For example, what are you hoping to achieve from this segment?

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

You're already using await on the result of the entire method, and you've used a Parallel.ForEach to get parallel execution of items in your loop, so what does the additional use of await Task.Run() get you? The code would certainly be a lot easier to follow without it.

It is not clear to me what you are trying to achieve here. Can you describe what your objectives are for this method?

Tim Long
  • 13,508
  • 19
  • 79
  • 147
  • Yea man my mindset is to put a async or Task.Run on every called method, and I think that what you're trying to tell me if I'm wrapping the entire body with return await Task.Run()=> I do not need to add any other async await keywords. So, where I am calling the async Task BuildUserFilePath() method there is no reason to do anything other than append .Result to the end of the calling location, because the current body of code is already in async mode.? I've update my post again, will you please let me know if I've structured it correctly and that it is truly running in async? – Filling The Stack is What I DO Jun 08 '13 at 03:06
  • Never mind I found the .Skip() in the expression. I've updated the main code to reflect this. – Filling The Stack is What I DO Jun 08 '13 at 19:39