![](/img/trans.png)
[英]UserManager error "A second operation started on this context before a previous operation completed"
[英]await on UserManager causes the error A second operation started on this context before a previous operation completed
背景
我正在开发一个.Net Core API来驱动一个歌词应用程序。 用户可以注册,提交艺术家和歌词,并在此过程中获得荣誉/ XP积分。 基本上是一个社区驱动的歌词网站。
码
这是我的ArtistController类:
[Route("api/artists")]
public class ArtistsController : Controller
{
private readonly IPermissionsService _permissionsService;
private readonly IArtistsService _artistsService;
public ArtistsController(IArtistsService artistsService, IPermissionsService permissionsService)
{
_permissionsService = permissionsService ?? throw new ArgumentNullException(nameof(permissionsService));
_artistsService = artistsService ?? throw new ArgumentNullException(nameof(artistsService));
}
[HttpGet("{slug}")]
[HttpGet("{slug}/lyrics", Name = "GetArtist")]
public async Task<IActionResult> GetArtist(string slug)
{
if (!_artistsService.ArtistExists(slug)) return NotFound();
var permissions = await _permissionsService.GetPermissions(HttpContext);
var artist = _artistsService.GetArtistBySlug(slug, permissions.UserId, permissions.IsAdministrator);
if (artist == null) return NotFound();
return Ok(artist);
}
// other methods omitted
}
本着可测试性的精神,我创建了一个IPermissionsService
,这种方式当我来单元测试控制器时,我可以轻松地这样做而不用担心HttpContext
和User
。
以下是PermissionsService
类的代码:
public class PermissionsService : IPermissionsService
{
private string _userId;
private bool _isAdministrator;
private HttpContext _httpContext;
private readonly UserManager<BbUser> _userManager;
public PermissionsService(UserManager<BbUser> userManager)
{
_userManager = userManager;
}
public async Task<Permissions> GetPermissions(HttpContext httpContext)
{
_httpContext = httpContext;
PopulateUserIdAndIsAdminFlag();
var permissions = new Permissions
{
UserId = _userId,
IsAdministrator = _isAdministrator
};
return await Task.Run(() => permissions);
}
private async void PopulateUserIdAndIsAdminFlag()
{
if (!IsAuthenticated()) return;
var username = _httpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
var user = await _userManager.FindByNameAsync(username);
var roles = await _userManager.GetRolesAsync(user);
_userId = user.Id;
_isAdministrator = roles.Contains("Admin");
}
private bool IsAuthenticated()
{
return _httpContext.User.Identity.IsAuthenticated;
}
}
问题
当我运行API并尝试调用该端点时。 我收到以下错误:
在上一个操作完成之前,在此上下文中开始第二个操作。 任何实例成员都不保证是线程安全的。
消息很清楚,但我不知道如何克服这个错误。 在将该逻辑移到PermissionsService
之前,我没有收到错误,一切正常!
执行async
工作的方法应该返回Task
而不是void
除非它们是事件处理程序。 这将允许等待生成的Task
。 因为没有等待PopulateUserIdAndIsAdminFlag
,所以您正在跨线程同时调用同一个DbContext
实例。 如果你按照调用堆栈:
GetPermissions
PopulateUserIdAndIsAdminFlag
开始工作, 但不要等待它完成 GetPermissions
返回( PopulateUserIdAndIsAdminFlag
代码仍在执行 ) _artistsService
上调用方法 这可能导致多个线程同时调用DbContext,从而导致异常。
修复代码,以便等待PopulateUserIdAndIsAdminFlag
的结果。
PopulateUserIdAndIsAdminFlag
方法,以便返回类型Task
PopulateUserIdAndIsAdminFlag
的结果 GetPermissions
结束时不再需要将结果包装在Task
中 Async
因为这被认为是返回类型Task
方法的正确命名约定。 这将导致名为GetPermissionsAsync
和PopulateUserIdAndIsAdminFlagAsync
方法 更改的代码:
public async Task<Permissions> GetPermissions(HttpContext httpContext)
{
_httpContext = httpContext;
// await result
await PopulateUserIdAndIsAdminFlag();
var permissions = new Permissions
{
UserId = _userId,
IsAdministrator = _isAdministrator
};
// wrapping the result in Task is no longer necessary
return permissions;
}
// change void to Task
private async Task PopulateUserIdAndIsAdminFlag()
{
if (!IsAuthenticated()) return;
var username = _httpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
var user = await _userManager.FindByNameAsync(username);
var roles = await _userManager.GetRolesAsync(user);
_userId = user.Id;
_isAdministrator = roles.Contains("Admin");
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.