【发布时间】:2021-10-23 08:11:24
【问题描述】:
我已经重构了我的代码,以尽量避免根据MS Instructions 和本网站上的其他帖子出现以下错误,它似乎略有改善,但如果我快速连续单击多个按钮,我我仍然收到以下错误:
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, however instance members are not guaranteed to be thread safe.
startup.cs
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
在我的控制器中:
readonly RGWIncrementUserActionClicks RGWIncrementClicks = new();
private readonly ApplicationDbContext _context;
public RGWController(IEmailSender emailSender, ApplicationDbContext context)
{
_emailSender = emailSender;
_context = context;
}
public async Task <IActionResult> RGWInsertNewActionNumber(int actionNumber)
{
string userId = ClaimsPrincipalExtensions.GetUserId<string>(User);
await RGWIncrementClicks.UserClick(actionNumber, userId, _context);
return RedirectToAction("RGW");
}
当await RGWIncrementClicks.UserClick(actionNumber, userId, _context);被触发时,UserClick方法(未显示)有一个switch语句,可以调用其他方法的组合,根据传入的actionNumber参数向DB添加值。
我使用 await 尝试了所有后续方法和调用,但仍然出现多线程错误。我也尝试过不使用它们,但仍然出现多线程错误。
我不明白的是await RGWIncrementClicks.UserClick(actionNumber, userId, _context);是所有使用dbContext的后续函数的入口点,它使用await,加上它为这个调用实例传入了注入的dbContext,所以我如何我在后续函数中遇到线程错误...它们应该只在此处等待入口点完成后触发第二次,表明所有后续函数都已完成...不是吗?
更新:我只是在想我实际上并不希望它是异步的,因为我希望 UI 在数据库更改后触发 RedirectToAction 时在每次用户点击时更新.所以也许这不是线程管理问题,而是用户管理问题....我应该防止用户在触发我的RGWInsertNewActionNumber 方法的任何按钮上单击两次...
Update.2 - 为了节省空间,我把它省略了:
public class RGWIncrementUserActionClicks
{
private static readonly IncrementSiteActions _IncrementSiteActions = new();
private static readonly IncrementUserActions _IncrementUserActions = new();
public async Task UserClick(int actionNumber, string userId, ApplicationDbContext _context)
{
var userActions = await _context.UserEnvironmentalActionCounts.FindAsync(userId);
var siteTotalActions = await _context.SiteEnvironmentalActionCounts.FirstOrDefaultAsync();
siteTotalActions.SiteGlobalWarmingClicks++;
switch (actionNumber)
{
case 1:
if (userActions != null)
{
await _IncrementUserActions.incrementUserActions(userId, userActions => { userActions.ReduceMeat++; userActions.UserReduceMeatCO2Total = userActions.UserReduceMeatCO2Total + 2.0075; }, userActions => { userActions.UserCO2Total = userActions.UserCO2Total + 2.0075; });
if (siteTotalActions != null)
{
await _IncrementSiteActions.incrementSiteActions(siteTotalActions => { siteTotalActions.SiteReduceMeat++; }, true, true, true);
}
else
{
await _context.SiteEnvironmentalActionCounts.AddAsync(new SiteEnvironmentalActionCounts { SiteTotal = 1, SiteGlobalWarmingTotal = 1, SiteReduceMeat = 1, SiteDeforestationTotal = 1, SiteExtinctionTotal = 1 });
}
}
else
{
await _context.UserEnvironmentalActionCounts.AddAsync(new UserEnvironmentalActionCounts { Id = userId, ReduceMeat = 1, UserTotal = 1, UserReduceMeatCO2Total = 2.0075, UserCO2Total = 2.0075 });
if (siteTotalActions != null)
{
await _IncrementSiteActions.incrementSiteActions(siteTotalActions => { siteTotalActions.SiteReduceMeat++; }, true, true, true);
}
else
{
await _context.SiteEnvironmentalActionCounts.AddAsync(new SiteEnvironmentalActionCounts { SiteTotal = 1, SiteGlobalWarmingTotal = 1, SiteReduceMeat = 1, SiteDeforestationTotal = 1, SiteExtinctionTotal = 1 });
}
}
await _context.SaveChangesAsync();
break;
case 2:
...etc.
default:
break;
}
}
}
}
错误详情:
Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken)
GatheringForGood.Areas.FunctionalLogic.IncrementUserActions.incrementUserActions(string userId, Action<UserEnvironmentalActionCounts> update, Action<UserEnvironmentalActionCounts> updateUserCO2Total) in IncrementUserActions.cs
+
23. await _context.SaveChangesAsync();
GatheringForGood.Areas.FunctionalLogic.RGWIncrementUserActionClicks.UserClick(int actionNumber, string userId, ApplicationDbContext _context) in RGWIncrementUserActionClicks.cs
+
30. await _IncrementUserActions.incrementUserActions(userId, userActions => { userActions.ReduceMeat++; userActions.UserReduceMeatCO2Total = userActions.UserReduceMeatCO2Total + 2.0075; }, userActions => { userActions.UserCO2Total = userActions.UserCO2Total + 2.0075; });
MyApp.Controllers.ReduceGlobalWarmingController.RGWInsertNewActionNumber(int actionNumber) in ReduceGlobalWarmingController.cs
+
354. await RGWIncrementClicks.UserClick(actionNumber, userId, _context);
Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor+TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, object controller, object[] arguments)
System.Threading.Tasks.ValueTask<TResult>.get_Result()
【问题讨论】:
-
你必须至少显示 RGWIncrementClicks.UserClick。
-
谢谢,现在更新@Serge
标签: c# asp.net thread-safety dbcontext