web-dev-qa-db-ja.com

前の非同期操作が完了する前に、このコンテキストで2番目の操作が開始されました

メッセージ:

"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."

コード:

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

私が試したもの:

私が間違っていることはわかりませんが、次のような別のものを試しました:

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. 他の試行錯誤、私は今すぐにしない

私は何を間違えていますか?

15
Gerald Hughes

問題は次のとおりです。

userLangs.ForEach(async

ForEachは非同期デリゲートを理解しないため、これはasync voidメソッドを作成しています。したがって、ForEachの本体は同時に実行され、Entity Frameworkは同時非同期アクセスをサポートしません。

ForEachforeachに変更すると、うまくいくはずです。

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

詳細については、私の 非同期ベストプラクティスの記事 の「avoid async void」ガイダンスを参照してください。

34
Stephen Cleary