Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Async method called from Task.WhenAll use DbContext and returned an error

Hi I need some advice on async method run by Task.WhenAll(). It's dotnet core 3 project and context and serviced are added as AddScoped.

When I run DoWorksAysnc method I always get an issue "A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext. For more information on how to avoid threading issues with DbContext"

I understand that DbContext is not thread safe. but is there anyway I can run the DowksAsync method Concurrently..?

Thanks,

   public async Task DoWorksAsync(List<int> ids)
        {
            
            if (ids.Count > 0)
            {
                var listOfTasks = new List<Task>();
                foreach(var id in ids)
                {
                    var task = Task.Run(async () => await UpdateDataById(id));
                    listOfTasks.Add(task);
                }
                await Task.WhenAll(listOfTasks);
            }
        }

And UpdateDataById method is

 public async Task UpdateDataById (int id)
        {
            try
            {

                //Get products from webservice
                List<Product> products = await _searchService.GetItemsByIdAsync(id);
                await _dataService.CreateTableIfNotExist(id); //Method inside is await _dbContext.Database.ExecuteSqlRawAsync(string.Format(_sqlCreateTableIfNotExist, id));


                // UpdateProductsToDataTable method includes truncate table and bulkcopy.writeToServerAsync(tablename)
                //await _dbContext.Database.ExecuteSqlRawAsync(string.Format("Truncate Table {0}", tableName));    
                //await bulkcopy.WriteToServerAsync();
              
                if (await _dataService.UpdateProductsToDataTable(id,products) == true) 
                {
                   await _emailService.sendEmailNotificationAsync();
                }


                _logService.AddLog("process is done"); 

                _unitOfWork.SaveAsync(); //_context.SaveChangesAsync()
            }
            catch (Exception ex)
            {
                await _logService.AddErrorLog(new ErrorLog
                {
                    Id= id,
                    ErrorMessage = ex.Message
                });
                _unitOfWork.SaveAsync();
            }
        }
like image 770
dbenbyeon Avatar asked Oct 31 '25 18:10

dbenbyeon


1 Answers

Although you know this, DbContexts are not thread safe, end of story..

The issue is common, and this fix is simple if you follow these rules of thumb

  1. Do not cache a DbContext, they are already cached internally. If you have a repository (and I really suggest your shouldn't), never hold the DbContext as an instance member unless you are absolutely certain it is thread safe and it is being used very discreetly for small workloads. However see point 2.
  2. Always put a DbContext in a using statement if you can.
  3. Never call an instance of a DbContext from multiple threads (they are not thread safe). See point 1 and 2 respectively
  4. If you find your self wanting to throw more threads at your Database (to speed it up), you are likely doing something wrong. Databases parallel their own workloads and query plans, and can do so generally better than you can (within reason). Write better queries and profile them. I.e. Instead of querying for each id separately, query for all ids at once (if you can)

If you really need to do this, ensure your DbContext is a separate instance for each thread through one means or another.

Or as suggested by Jeremy Lakeman. Make sure the context is unique for each method call.

like image 141
TheGeneral Avatar answered Nov 03 '25 09:11

TheGeneral