35

Message:

"System.NotSupportedException was unhandled
Message: An unhandled exception of type 'System.NotSupportedException' occurred in mscorlib.dll
Additional information: A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe."

Code:

public async Task<IEnumerable<UserLangDTO>> ImportLang(int userId)
{
    var userLangs = new List<UserLangDTO>();
    using (FirstContext ctx = new FirstContext())
    {
        if (await (ctx.UserLang.AnyAsync(u => u.UserId == userId)) == false)
            //some exception here

        userLangs = await ctx.UserLang.AsNoTracking()
                                .Where(ul => ul.UserId == userId)
                                .Join(ctx.Language,
                                    u => u.LangID,
                                    l => l.LangID,
                                    (u, l) => new { u, l })
                                .Join(ctx.Level,
                                    ul => ul.u.LevelID,
                                    le => le.LevelID,
                                    (ul, le) => new { ul, le })
                                .Select(r => new UserLangDTO
                                {
                                UserId = r.ul.u.UserId,
                                Language = r.ul.l.Language,
                                Level = r.le.Level,
                                }).ToListAsync().ConfigureAwait(false);

    }
    using (SecondContext ctx = new SecondContext())
    {
        if ( await (ctx.UserLangs.AnyAsync(u => u.UserId == userId)) == true && userLangs.Any())
            ctx.UserLangs.RemoveRange(ctx.UserLangs.Where(u => u.UserId == userId));
        if (await hasUserLangs && userLangs.Any())
        {
            userLangs.ForEach(async l =>
            {
                var userLanguage = new UserLang();
                userLanguage.UserId = userId;
                userLanguage.LanguageId = await ctx.Languages.AsNoTracking()
                                                 .Where(la => la.NameEn == l.Language)
                                                 .Select(la => la.Id).FirstOrDefaultAsync().ConfigureAwait(false);
                userLanguage.LevelId = await ctx.Levels.AsNoTracking()
                                                .Where(la => la.NameEn == l.Language)
                                                .Select(la => la.Id).FirstOrDefaultAsync().ConfigureAwait(false);

                ctx.UserLangs.Add(userLanguage);
            });
        }
        await ctx.SaveChangesAsync().ConfigureAwait(false);
    }
    return userLangs;
}

What I Tried:

I'm not sure what I'm doing wrong, I tried different stuff like :

1.

await Task.Run(() => Parallel.ForEach(strings, s =>
{
    DoSomething(s);
})); 

2.

var tasks = userLangs.Select(async l =>
{
    //rest of the code here
}
await Task.WhenAll(tasks); 

3.

var tasks = userLangs.Select(async l =>
{
    //rest of the code here
}
await Task.WhenAll(tasks);

await ctx.SaveChangesAsync().ConfigureAwait(false); 
  1. Other trial and error attempts, that i do not rember now

What am I doing wrong?

Gerald Hughes
  • 5,771
  • 20
  • 73
  • 131

2 Answers2

76

Here's your problem:

userLangs.ForEach(async

This is creating an async void method, because ForEach does not understand asynchronous delegates. So the body of the ForEach will be run concurrently, and Entity Framework does not support concurrent asynchronous access.

Change the ForEach to a foreach, and you should be good:

foreach (var l in userLangs)
{
  var userLanguage = new UserLang();
  userLanguage.UserId = userId;
  userLanguage.LanguageId = await ...
}

For more information, see the "avoid async void" guidance in my Async Best Practices article.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
1

For those who have the ability to use .NET 6, there is now a Parallel.ForEachAsync()

https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.parallel.foreachasync?view=net-6.0 https://www.hanselman.com/blog/parallelforeachasync-in-net-6

jlavallet
  • 1,267
  • 1
  • 12
  • 33