Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] 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. #21987

Closed
davidbuckleyni opened this issue Aug 7, 2020 · 24 comments
Labels
closed-no-further-action The issue is closed and no further action is planned. customer-reported

Comments

@davidbuckleyni
Copy link

davidbuckleyni commented Aug 7, 2020

I am using ef core 3.1.5 and asp.net core 3.1 and am having this constant issue around a function that sets a view bag in the edit and create methods.

Strange thing is this only happens in production on the hosted server, i dont get this error locally

    services.AddDbContext<MISDBContext>
        (options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")), ServiceLifetime.Scoped);
    
        public async Task<int> GetNotificationsCount() {
            var userId =  GetCurrentTennantId().Result;
            //the not should show all the notifcations should only check the desnitation user id
            return _context.Notifications.Where(w => w.SharedTo == userId.ToString()).Count();
        }
 

This setup view bag would be called on functions like edit and create

 public async void SetupViewBags() {
 ViewBag.NotificationCount = await GetNotificationsCount();

   }

For Example I would call it as such

  // GET: MISObjects/Create
    public IActionResult Create() {
        SetupViewBags();

        return View();
    }

Got Exceptions? Include both the message and the stack trace

info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
Request finished in 165.3482ms 200 application/javascript
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (1ms) [Parameters=[@__ToString_0='?' (Size = 450)], CommandType='Text', CommandTimeout='30']
SELECT COUNT(*)
FROM [MISobject] AS [m]
WHERE (([m].[OIC_1] = @__ToString_0) OR ([m].[OIC_2] = @__ToString_0)) AND (([m].[isDeleted] = CAST(0 AS bit)) AND ([m].[isActive] = CAST(1 AS bit)))
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
Request finished in 163.0282ms 200 application/javascript
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT [a].[FirstName], [a].[LastName], [a].[Id]
FROM [AspNetUsers] AS [a]
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT [a].[Id], [a].[AccessFailedCount], [a].[ConcurrencyStamp], [a].[Email], [a].[EmailConfirmed], [a].[FirstName], [a].[LastName], [a].[LockoutEnabled], [a].[LockoutEnd], [a].[NormalizedEmail], [a].[NormalizedUserName], [a].[PasswordHash], [a].[PhoneNumber], [a].[PhoneNumberConfirmed], [a].[SecurityStamp], [a].[TennantId], [a].[TwoFactorEnabled], [a].[UserName]
FROM [AspNetUsers] AS [a]
fail: Microsoft.EntityFrameworkCore.Query[10100]
An exception occurred while iterating over the results of a query for context type 'MISSystem.Dal.MISDBContext'.
System.InvalidOperationException: 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, see https://go.microsoft.com/fwlink/?linkid=2097913.
at Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()
at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable1.AsyncEnumerator.MoveNextAsync() System.InvalidOperationException: 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, see https://go.microsoft.com/fwlink/?linkid=2097913. at Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection() at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable1.AsyncEnumerator.MoveNextAsync()
info: Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware[2]
Sending file. Request path: '/font-awesome/js/all.js'. Physical path: 'D:\GitMaster\MIS\uno\MISSystem.WEB\MISSystem.Web\MISSystem.Web\wwwroot\font-awesome\js\all.js'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]

For dotnet ef and PMC, share the --verbose output
An exception occurred while iterating over the results of a query for context type 'MISSystem.Dal.MISDBContext'.
System.InvalidOperationException: 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, see https://go.microsoft.com/fwlink/?linkid=2097913.
at Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()
at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable1.Enumerator.MoveNext() System.InvalidOperationException: 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, see https://go.microsoft.com/fwlink/?linkid=2097913. at Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection() at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable1.Enumerator.MoveNext()
Unhandled exception. System.InvalidOperationException: 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, see https://go.microsoft.com/fwlink/?linkid=2097913.
at Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()
at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable1.Enumerator.MoveNext() at System.Linq.Enumerable.Single[TSource](IEnumerable1 source)
at lambda_method(Closure , QueryContext )
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
at System.Linq.Queryable.Count[TSource](IQueryable1 source) at MISSystem.Web.Controllers.MISObjectsController.GetNotificationsCount() in D:\GitMaster\MIS\uno\MISSystem.WEB\MISSystem.Web\MISSystem.Web\Controllers\MISObjectsController.cs:line 115 at MISSystem.Web.Controllers.MISObjectsController.SetupViewBags() in D:\GitMaster\MIS\uno\MISSystem.WEB\MISSystem.Web\MISSystem.Web\Controllers\MISObjectsController.cs:line 405 at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__139_1(Object state) at System.Threading.QueueUserWorkItemCallback.<>c.<.cctor>b__6_0(QueueUserWorkItemCallback quwi) at System.Threading.ExecutionContext.RunForThreadPoolUnsafe[TState](ExecutionContext executionContext, Action1 callback, TState& state)
at System.Threading.QueueUserWorkItemCallback.Execute()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()

Further technical details

EF Core version:
Database provider: (e.g. Microsoft.EntityFrameworkCore.SqlServer)
Target framework: (e.g. .NET Core 3.0)
Operating system:
IDE: (e.g. Visual Studio 2019 16.3)

@davidbuckleyni davidbuckleyni changed the title 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. [BUG] 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. Aug 7, 2020
@davidroth
Copy link
Contributor

davidroth commented Aug 7, 2020

You are not correctly using async/await.
To fix your issues:

Change the SetupViewBags signature from async void to async Task.

Then in your controller, properly await it and change the controller action signature as following:

public async Task<IActionResult> Create() 
{
   await SetupViewBags();
   return View();
}

Furthermore you should not block in GetNotificationsCount. Use await and don't use .Result:

var userId = await GetCurrentTennantId();
return await _context.Notifications.Where(w => w.SharedTo == userId.ToString()).CountAsync();

Check your other code for these patterns and fix accordingly.

@ajcvickers ajcvickers added the closed-no-further-action The issue is closed and no further action is planned. label Aug 10, 2020
@andersonphiri
Copy link

I had the same error using ef-core in wpf with autofact. if using Ctor injection in wpf the issue seems to go away if I inject a Func in the constructor rather than injecting repository

@mzuvin
Copy link

mzuvin commented Mar 11, 2021

@andersonphiri Yes the problem is autofac. The problem was fixed when Autofac did InstancePerLifetimeScope () instead of SingleInstance (). Thanks for your answer.

@tnlthanzeel
Copy link

tnlthanzeel commented May 22, 2021

i have a list task that i finally await to get the result. it worked fine with efcore 2.1. today i updated my project to .NET 5. im using EF core 5. im getting the error "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".

 var taskList = new List<Task<PaginationModel<SellerPaymentHistoryModel>>>();

  taskList.Add( _sellerPaymentService.GetSellerPaymentHistoryAsync(sellerPaymentHistoryFilterModel, skip, take));

                taskList.Add(_salePaymentsService.GetBuyerPaymentHistory(sellerPaymentHistoryFilterModel, skip, take));

                taskList.Add(_miscPaymentService.GetMiscPaymentHistoryAsync(sellerPaymentHistoryFilterModel, skip, take));

// throws error on the below line
 var resutls=await Task.WhenAll(taskList);
// all the methods have this format

public async Task<PaginationModel<SellerPaymentHistoryModel>> GetSellerPaymentHistoryAsync(SellerPaymentHistoryFilterModel sellerPaymentHistoryFilterModel, int skipe, int take)
        {
            var query = gemStoContext.Transactions.Include(i => i.Gem).ThenInclude(i => i.Seller)
                                     .Where(w => !w.IsDeleted && w.Remark != Remark.Deleted && w.Gem.GemStatus != GemStatus.Returned &&
                                            w.TransactionType == TransactionType.SellerPayment &&
                                            w.PaidAmount != 0.00m
                                           );


            if (!string.IsNullOrEmpty(sellerPaymentHistoryFilterModel.SearchQuery))
            {
                query = query.Where(w =>
                                    EF.Functions.Like(w.Gem.SellerName, sellerPaymentHistoryFilterModel.SearchQuery + "%") ||
                                    EF.Functions.Like(w.Gem.Seller.Name, sellerPaymentHistoryFilterModel.SearchQuery + "%") ||
                                    EF.Functions.Like(w.Gem.StockNumber, sellerPaymentHistoryFilterModel.SearchQuery + "%") ||
                                    EF.Functions.Like(w.PaidAmount.ToString(), sellerPaymentHistoryFilterModel.SearchQuery + "%")
                                   );
            }

            if (sellerPaymentHistoryFilterModel.FromDate.HasValue)
            {
                query = query.Where(w => w.PaidOn.Value.SetClientTimeZone().Date >= sellerPaymentHistoryFilterModel.FromDate.Value.SetClientTimeZone().Date);
            }

            if (sellerPaymentHistoryFilterModel.ToDate.HasValue)
            {
                query = query.Where(w => w.PaidOn.Value.SetClientTimeZone().Date <= sellerPaymentHistoryFilterModel.ToDate.Value.SetClientTimeZone().Date);
            }

            var totalCount = await query.CountAsync();

            query = query.OrderByDescending(o => o.PaidOn).Skip(skipe).Take(take);

            var result = query.Select(s => new SellerPaymentHistoryModel
            {
                SellerName = s.Gem.SellerId.HasValue ? s.Gem.Seller.Name : s.Gem.SellerName,
                StockNumber = s.Gem.StockNumber,
                PaidAmount = s.PaidAmount,
                PaidOn = (DateTimeOffset)s.PaidOn,
                IsSinglePurchase = s.GemStatus == GemStatus.GemLot ? false : true,
                GemId = s.GemId,
                GemIdentity = s.Gem.Identity,
                PaymentHistoryType = PaymentHistoryType.Seller,
                Id = s.Id,
                Description = s.Description
            });


            var resultData = await result.AsNoTracking().ToListAsync();

            var resultSet = new PaginationModel<SellerPaymentHistoryModel>()
            {
                Details = resultData,
                TotalRecords = totalCount
            };

            return resultSet;
        }

please help @roji

@andersonphiri
Copy link

hi @tnlthanzeel DbContext is not thread safe. it is recommended to use DI then do a ctor Injection. Dont forget to register in startup CS file. If you don't mind, please share the repo then i checkout your code and see if I can re-create and fix.

@tnlthanzeel
Copy link

tnlthanzeel commented May 22, 2021

hi @tnlthanzeel DbContext is not thread safe. it is recommended to use DI then do a ctor Injection. Dont forget to register in startup CS file. If you don't mind, please share the repo then i checkout your code and see if I can re-create and fix.

hi @andersonphiri , thanks for the response. the repo is private.

this is the Startup.cs setup as follows

services.AddIdentity<StoreUser, IdentityRole>(cfg =>
           {
               // user the below optio to make the email unique
               // cfg.User.RequireUniqueEmail = true 

               // user the below options to make the password policy
               //cfg.Password.RequireDigit = true;
               //cfg.Password...
           }).AddEntityFrameworkStores<GemStoContext>();

services.AddDbContext<GemStoContext>(cfg =>
           {
               cfg.UseSqlServer(configuration.GetConnectionString("GemStoContext"));
           });

i have injected the dbcontext in the constructor using DI

public SellerPaymentService(GemStoContext gemStoContext, IMapper mapper)
       {
           this.gemStoContext = gemStoContext;
           this.mapper = mapper;
       }

this has been working well on ef core 2.1

i tried with Transient scope as well. but still fails with same error

@andersonphiri
Copy link

I see. then execute your tasks in sequence. I suspect there is one spot where your queries are overlapping on a particular entity.

@tnlthanzeel
Copy link

tnlthanzeel commented May 22, 2021

I see. then execute your tasks in sequence. I suspect there is one spot where your queries are overlapping on a particular entity.

yes, executing tasks in sequence worked. but i want to make use of the Task.WhenAll() as i have been using in ef core 2.1. please help

all the 3 methods have three different entities

@andersonphiri
Copy link

try inject multiple dbCOntexts maybe you can win this race condition issue. but watch out for DbUpdateException

@tnlthanzeel
Copy link

try inject multiple dbCOntexts maybe you can win this race condition issue. but watch out for DbUpdateException

each service has its own dbcontext injected in the ctor

@tnlthanzeel
Copy link

tnlthanzeel commented May 22, 2021

try inject multiple dbCOntexts maybe you can win this race condition issue. but watch out for DbUpdateException

i found have a place where one of my team mate has added

services.AddScoped<GemStoContext, GemStoContext>();

so now i removed it and the Task.WhenAll() works only under transient ServiceLifetime. why does it not work in scoped ServiceLifetime as in ef core 2.1?

please help @ErikEJ

@ErikEJ
Copy link
Contributor

ErikEJ commented May 22, 2021

Maybe you were not running the query on the database with EF Core 2.1?

@tnlthanzeel
Copy link

tnlthanzeel commented May 22, 2021

Maybe you were not running the query on the database with EF Core 2.1?

hi sir. it has been running on production with ef core 2.1 for 3 years.

here is a similar issue with ef core with MySql

PomeloFoundation/Pomelo.EntityFrameworkCore.MySql#1023

im just curios if this issue was in ef core 2.1 https://docs.microsoft.com/en-us/ef/core/dbcontext-configuration/#avoiding-dbcontext-threading-issues and was not addressed?

please help me @roji

@roji
Copy link
Member

roji commented May 22, 2021

@tnlthanzeel what @lauxjpn writes in PomeloFoundation/Pomelo.EntityFrameworkCore.MySql#1023 is fully right - EF Core has never supported using the same DbContext instance concurrently from multiple threads. Some code using concurrent access may have accidentally worked in some situations, but only because it happened to not trigger an exception due to timing.

You'll have to ensure that you're not using the instance concurrently, either by serializing your queries, or by using multiple DbContext instances (which would represent multiple database connections).

If you'd like to pursue this further, we need a minimal, runnable code sample.

@lauxjpn
Copy link
Contributor

lauxjpn commented May 22, 2021

try inject multiple dbCOntexts maybe you can win this race condition issue. but watch out for DbUpdateException

each service has its own dbcontext injected in the ctor

try inject multiple dbCOntexts maybe you can win this race condition issue. but watch out for DbUpdateException

i found have a place where one of my team mate has added

services.AddScoped<GemStoContext, GemStoContext>();

so now i removed it and the Task.WhenAll() works only under transient ServiceLifetime. why does it not work in scoped ServiceLifetime as in ef core 2.1?

If you inject a DbContext into 3 different services, and your DbContext class was registered as scoped, then your 3 different services will get a reference to the same (single) DbContext object.

On the other hand, if you register your DbContext class as transient, your 3 different services will get a reference to 3 different DbContext instances. So it works because your services then don't call into the same DbContext instance at the same time in parallel.

@tnlthanzeel If your code runs fine with the transient approach, then consider using it.

Another way to achieve the same would be to keep the DbContext scoped, but then create individual scopes for your services, so that each of your services ends up with their own distinct context again in the end. This makes sense, if you only want your 3 services to use different contexts, so they can run in parallel, but generally want to use the same DbContext instance in other places of your app.

I am curious what your reason for your optimization is:

  • Is the physical machine that runs the database at a different location than the web server (far away, e.g. half around the world) and accessing it results in a high overhead because of latency and your users have to wait a painfully long time for a web request to respond, so you want to solve the issue by executing as many database operations in parallel as possible, so the database server latency impacts you only once per web request?
  • The performance of a web request is fine, but you want to increase the web request throughput your application can handle on its current hardware?

(Because there might be better solutions to the underlying problem, depending on what it actually is.)

@roji
Copy link
Member

roji commented May 23, 2021

Just to add to @lauxjpn great response above, if you generally want to keep DbContext as Scoped, you can still get injected with an IDbContextFactory in the specific place where you need multiple instances (see the docs). That would probably be a bit easier/cleaner than creating DI scopes yourself.

@lauxjpn
Copy link
Contributor

lauxjpn commented May 23, 2021

[...] if you generally want to keep DbContext as Scoped, you can still get injected with an IDbContextFactory in the specific place where you need multiple instances [...]

@roji Nice one! I'll keep that in mind for my own projects :)

@tnlthanzeel
Copy link

tnlthanzeel commented May 23, 2021

@tnlthanzeel what @lauxjpn writes in PomeloFoundation/Pomelo.EntityFrameworkCore.MySql#1023 is fully right - EF Core has never supported using the same DbContext instance concurrently from multiple threads. Some code using concurrent access may have accidentally worked in some situations, but only because it happened to not trigger an exception due to timing.

You'll have to ensure that you're not using the instance concurrently, either by serializing your queries, or by using multiple DbContext instances (which would represent multiple database connections).

If you'd like to pursue this further, we need a minimal, runnable code sample.

Hi @roji , i understand. but the same code doesnt work with EF Core 5 which worked in EF Core 2.1. Here is a a repo with minimul code which runs fine on EF Core 2.1 https://github.com/tnlthanzeel/EFCore-TaskWhenAll. Swagger is installed. go to http://localhost:{yourportnumber}/swagger

@tnlthanzeel
Copy link

try inject multiple dbCOntexts maybe you can win this race condition issue. but watch out for DbUpdateException

each service has its own dbcontext injected in the ctor

try inject multiple dbCOntexts maybe you can win this race condition issue. but watch out for DbUpdateException

i found have a place where one of my team mate has added
services.AddScoped<GemStoContext, GemStoContext>();
so now i removed it and the Task.WhenAll() works only under transient ServiceLifetime. why does it not work in scoped ServiceLifetime as in ef core 2.1?

If you inject a DbContext into 3 different services, and your DbContext class was registered as scoped, then your 3 different services will get a reference to the same (single) DbContext object.

On the other hand, if you register your DbContext class as transient, your 3 different services will get a reference to 3 different DbContext instances. So it works because your services then don't call into the same DbContext instance at the same time in parallel.

@tnlthanzeel If your code runs fine with the transient approach, then consider using it.

Another way to achieve the same would be to keep the DbContext scoped, but then create individual scopes for your services, so that each of your services ends up with their own distinct context again in the end. This makes sense, if you only want your 3 services to use different contexts, so they can run in parallel, but generally want to use the same DbContext instance in other places of your app.

I am curious what your reason for your optimization is:

  • Is the physical machine that runs the database at a different location than the web server (far away, e.g. half around the world) and accessing it results in a high overhead because of latency and your users have to wait a painfully long time for a web request to respond, so you want to solve the issue by executing as many database operations in parallel as possible, so the database server latency impacts you only once per web request?
  • The performance of a web request is fine, but you want to increase the web request throughput your application can handle on its current hardware?

(Because there might be better solutions to the underlying problem, depending on what it actually is.)

Hi @lauxjpn , thanks for sharing your knowledge. the web server and the db server are on the same location. What i did was to implement Task.WhenAll() which i learnt. i applied the theory which i thought was a best place to apply.

@lauxjpn
Copy link
Contributor

lauxjpn commented May 23, 2021

@tnlthanzeel I see. There are many technologies that work well with that approach, but since a DbContext only supports one call at a time, the recommended pattern is to just await multiple calls in sequence, instead of in parallel, because complexity can increase quickly, with more than one DbContext being involved. I would only go this road if necessary (e.g. due to performance issues) and even then only when other options are not more effective (e.g. a locally available read replica to get rid of latency).
For more information, see Asynchronous Programming and its warning boxes in the EF Core docs.

(In my personal experience, simplicity is usually the greatest quality that source code can provide, because it positively effects the app in so many different ways. Once simplicity is gone, it will be much harder later to get it back again.)

@tnlthanzeel
Copy link

@tnlthanzeel I see. There are many technologies that work well with that approach, but since a DbContext only supports one call at a time, the recommended pattern is to just await multiple calls in sequence, instead of in parallel, because complexity can increase quickly, with more than one DbContext being involved. I would only go this road if necessary (e.g. due to performance issues) and even then only when other options are not more effective (e.g. a locally available read replica to get rid of latency).
For more information, see Asynchronous Programming and its warning boxes in the EF Core docs.

(In my personal experience, simplicity is usually the greatest quality that source code can provide, because it positively effects the app in so many different ways. Once simplicity is gone, it will be much harder later to get it back again.)

@lauxjpn thanks for the advice. no i understand better. i never knew the fact we cannot use parallel programming on a dbcontext. just cam to know that just within 24 hours. thanks again sir

@upizs
Copy link

upizs commented Aug 24, 2021

I wanted to add that in my case I forgot to await _userManager.FunctionAsync(). So if you are having this error have a look around if you have put your await everywhere. ;)

@lauxjpn
Copy link
Contributor

lauxjpn commented Sep 5, 2021

I wanted to add that in my case I forgot to await _userManager.FunctionAsync(). So if you are having this error have a look around if you have put your await everywhere. ;)

@upizs If you are calling an async method without await in your code, the compiler should output a CS4014 warning in most of those cases, which is usually the quickest way to find the line responsible for the issue.

@ajcvickers ajcvickers reopened this Oct 16, 2022
@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Oct 16, 2022
@mikeDev-prog
Copy link

mikeDev-prog commented Dec 10, 2022

That is telling you that there are unnecessary requests that are been sent to the DbContext
so use "await Task.Delay(3000);" about you code the is sending the request to the DbContext

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-no-further-action The issue is closed and no further action is planned. customer-reported
Projects
None yet
Development

No branches or pull requests