繁体   English   中英

在UserManager上等待导致错误在上一个操作完成之前,在此上下文中启动了第二个操作

[英]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 ,这种方式当我来单元测试控制器时,我可以轻松地这样做而不用担心HttpContextUser

以下是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实例。 如果你按照调用堆栈:

  1. 代码进入GetPermissions
  2. 您可以在PopulateUserIdAndIsAdminFlag开始工作, 但不要等待它完成
  3. 代码立即从GetPermissions返回( PopulateUserIdAndIsAdminFlag代码仍在执行
  4. 代码继续并在_artistsService上调用方法

这可能导致多个线程同时调用DbContext,从而导致异常。

修复代码,以便等待PopulateUserIdAndIsAdminFlag的结果。

  • 将代码更改为等待PopulateUserIdAndIsAdminFlag方法,以便返回类型Task
  • 等待PopulateUserIdAndIsAdminFlag的结果
  • GetPermissions结束时不再需要将结果包装在Task
  • 我还建议重命名它并添加后缀Async因为这被认为是返回类型Task方法的正确命名约定。 这将导致名为GetPermissionsAsyncPopulateUserIdAndIsAdminFlagAsync方法

更改的代码:

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM